Browse Source

Started to fix the conversion flow. (#519)

* Started to fix the conversion flow.

* Finalized (hopefully)

* Improved tests and fixed a bug with assets resolver.
pull/520/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
59951d28d7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      backend/Squidex.ruleset
  2. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  3. 22
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  4. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs
  5. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs
  6. 151
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  7. 308
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  8. 53
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldIdentifier.cs
  9. 16
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs
  10. 142
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  11. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  12. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
  13. 16
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/DataConverter.cs
  14. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  15. 16
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  16. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs
  17. 19
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  18. 2
      backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs
  19. 10
      backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs
  20. 5
      backend/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs
  21. 10
      backend/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs
  22. 5
      backend/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs
  23. 19
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs
  24. 20
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs
  25. 74
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
  26. 273
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
  27. 90
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs
  28. 10
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
  29. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
  30. 33
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
  31. 86
      backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs

1
backend/Squidex.ruleset

@ -86,6 +86,7 @@
<Rule Id="AD0001" Action="None" />
</Rules>
<Rules AnalyzerId="Roslyn.Core" RuleNamespace="Microsoft.CodeAnalysis.Diagnostics">
<Rule Id="IDE0070" Action="None" />
<Rule Id="IDE0032" Action="None" />
<Rule Id="IDE0042" Action="None" />
</Rules>

5
backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs

@ -25,6 +25,11 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
protected ContentData(ContentData<T> source, IEqualityComparer<T> comparer)
: base(source, comparer)
{
}
protected ContentData(int capacity, IEqualityComparer<T> comparer)
: base(capacity, comparer)
{

22
backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -19,6 +19,16 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
public ContentFieldData(ContentFieldData source)
: base(source, StringComparer.OrdinalIgnoreCase)
{
}
public ContentFieldData(int capacity)
: base(capacity, StringComparer.OrdinalIgnoreCase)
{
}
public ContentFieldData AddValue(object? value)
{
return AddJsonValue(JsonValue.Create(value));
@ -52,6 +62,18 @@ namespace Squidex.Domain.Apps.Core.Contents
return this;
}
public ContentFieldData Clone()
{
var clone = new ContentFieldData(Count);
foreach (var (key, value) in this)
{
clone[key] = value?.Clone()!;
}
return clone;
}
public override bool Equals(object? obj)
{
return Equals(obj as ContentFieldData);

17
backend/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs

@ -17,6 +17,11 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
public NamedContentData(NamedContentData source)
: base(source, StringComparer.Ordinal)
{
}
public NamedContentData(int capacity)
: base(capacity, StringComparer.Ordinal)
{
@ -46,6 +51,18 @@ namespace Squidex.Domain.Apps.Core.Contents
return this;
}
public NamedContentData Clone()
{
var clone = new NamedContentData(Count);
foreach (var (key, value) in this)
{
clone[key] = value?.Clone()!;
}
return clone;
}
public bool Equals(NamedContentData other)
{
return base.Equals(other);

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
public static class Fields
{
public static RootField<ArrayFieldProperties> Array(long id, string name, Partitioning partitioning, params NestedField[] fields)
public static ArrayField Array(long id, string name, Partitioning partitioning, params NestedField[] fields)
{
return new ArrayField(id, name, partitioning, fields);
}

151
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -5,35 +5,45 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
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 static class ContentConverter
{
private static readonly Func<IRootField, string> KeyNameResolver = f => f.Name;
private static readonly Func<IRootField, long> KeyIdResolver = f => f.Id;
public static NamedContentData ConvertId2Name(this IdContentData content, Schema schema, params FieldConverter[] converters)
{
Guard.NotNull(schema);
var result = new NamedContentData(content.Count);
return ConvertInternal(content, result, schema.FieldsById, KeyNameResolver, converters);
}
foreach (var (fieldId, data) in content)
{
if (data == null || !schema.FieldsById.TryGetValue(fieldId, out var field))
{
continue;
}
public static IdContentData ConvertId2Id(this IdContentData content, Schema schema, params FieldConverter[] converters)
{
Guard.NotNull(schema);
ContentFieldData? newData = data;
var result = new IdContentData(content.Count);
ConvertArray(newData, field, FieldIdentifier.ById, FieldIdentifier.ByName);
if (newData != null)
{
newData = ConvertData(converters, field, newData);
}
if (newData != null)
{
result.Add(field.Name, newData);
}
}
return ConvertInternal(content, result, schema.FieldsById, KeyIdResolver, converters);
return result;
}
public static NamedContentData ConvertName2Name(this NamedContentData content, Schema schema, params FieldConverter[] converters)
@ -42,60 +52,117 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
var result = new NamedContentData(content.Count);
return ConvertInternal(content, result, schema.FieldsByName, KeyNameResolver, converters);
foreach (var (fieldName, data) in content)
{
if (data == null || !schema.FieldsByName.TryGetValue(fieldName, out var field))
{
continue;
}
ContentFieldData? newData = data;
if (newData != null)
{
newData = ConvertData(converters, field, newData);
}
if (newData != null)
{
result.Add(field.Name, newData);
}
}
return result;
}
public static IdContentData ConvertName2Id(this NamedContentData content, Schema schema, params FieldConverter[] converters)
public static IdContentData ConvertName2IdCloned(this NamedContentData content, Schema schema, params FieldConverter[] converters)
{
Guard.NotNull(schema);
var result = new IdContentData(content.Count);
return ConvertInternal(content, result, schema.FieldsByName, KeyIdResolver, converters);
foreach (var (fieldName, data) in content)
{
if (data == null || !schema.FieldsByName.TryGetValue(fieldName, out var field))
{
continue;
}
ContentFieldData? newData = data.Clone();
if (newData != null)
{
newData = ConvertData(converters, field, newData);
}
if (newData != null)
{
ConvertArray(newData, field, FieldIdentifier.ByName, FieldIdentifier.ById);
}
if (newData != null)
{
result.Add(field.Id, newData);
}
}
return result;
}
private static TDict2 ConvertInternal<TKey1, TKey2, TDict1, TDict2>(
TDict1 source,
TDict2 target,
IReadOnlyDictionary<TKey1, RootField> fields,
Func<IRootField, TKey2> targetKey, params FieldConverter[] converters)
where TDict1 : IDictionary<TKey1, ContentFieldData?>
where TDict2 : IDictionary<TKey2, ContentFieldData?>
where TKey1 : notnull
where TKey2 : notnull
private static ContentFieldData? ConvertData(FieldConverter[] converters, IRootField field, ContentFieldData data)
{
foreach (var (fieldName, value) in source)
if (converters != null)
{
if (!fields.TryGetValue(fieldName, out var field))
for (var i = 0; i < converters.Length; i++)
{
continue;
data = converters[i](data!, field)!;
if (data == null)
{
break;
}
}
}
var newValue = value;
return data;
}
if (newValue != null)
private static void ConvertArray(ContentFieldData data, IRootField? field, FieldIdentifier sourceIdentifier, FieldIdentifier targetIdentifier)
{
if (field is IArrayField arrayField)
{
foreach (var (key, value) in data)
{
if (converters != null)
if (value is JsonArray array)
{
foreach (var converter in converters)
foreach (var nested in array.OfType<JsonObject>())
{
newValue = converter(newValue, field);
var properties = nested.ToList();
if (newValue == null)
nested.Clear();
foreach (var (nestedKey, nestedValue) in properties)
{
break;
if (nestedValue == null)
{
continue;
}
var nestedField = sourceIdentifier.GetField(arrayField, nestedKey);
if (nestedField == null)
{
continue;
}
var targetKey = targetIdentifier.GetStringKey(nestedField);
nested[targetKey] = nestedValue;
}
}
}
}
if (newValue != null)
{
target.Add(targetKey(field), newValue);
}
}
return target;
}
}
}
}

308
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -23,162 +23,51 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static class FieldConverters
{
private delegate string FieldKeyResolver(IField field);
public static readonly FieldConverter Noop = (data, field) => data;
private static readonly FieldKeyResolver KeyNameResolver = f => f.Name;
private static readonly FieldKeyResolver KeyIdResolver = f => f.Id.ToString();
private delegate IField? FieldResolver(IArrayField field, string key);
private static IField? FieldByIdResolver(IArrayField array, string key)
public static readonly FieldConverter ExcludeHidden = (data, field) =>
{
if (key != null && long.TryParse(key, out var id))
{
return array.FieldsById.GetOrDefault(id);
}
return null;
}
return field.IsForApi() ? data : null;
};
private static IField? FieldByNameResolver(IArrayField array, string key)
public static readonly FieldConverter ExcludeChangedTypes = (data, field) =>
{
if (key != null)
foreach (var value in data.Values)
{
return array.FieldsByName.GetOrDefault(key);
}
return null;
}
public static FieldConverter ExcludeHidden()
{
return (data, field) => !field.IsForApi() ? null : data;
}
public static FieldConverter ExcludeChangedTypes()
{
return (data, field) =>
{
foreach (var value in data.Values)
if (value.Type == JsonValueType.Null)
{
if (value.Type == JsonValueType.Null)
{
continue;
}
try
{
var (_, error) = JsonValueConverter.ConvertValue(field, value);
if (error != null)
{
return null;
}
}
catch
{
return null;
}
continue;
}
return data;
};
}
public static FieldConverter ResolveAssetUrls(IReadOnlyCollection<string>? fields, IUrlGenerator urlGenerator)
{
if (fields?.Any() != true)
{
return (data, field) => data;
}
bool ShouldHandle(IField field, IField? parent = null)
{
if (field is IField<AssetsFieldProperties>)
try
{
if (fields.Contains("*"))
{
return true;
}
var (_, error) = JsonValueConverter.ConvertValue(field, value);
if (parent == null)
{
return fields.Contains(field.Name);
}
else
if (error != null)
{
return fields.Contains($"{parent.Name}.{field.Name}");
return null;
}
}
return false;
}
void Resolve(IJsonValue value)
{
if (value is JsonArray array)
catch
{
for (var i = 0; i < array.Count; i++)
{
var id = array[i].ToString();
array[i] = JsonValue.Create(urlGenerator.AssetContent(Guid.Parse(id)));
}
return null;
}
}
return (data, field) =>
{
if (ShouldHandle(field))
{
foreach (var partition in data)
{
Resolve(partition.Value);
}
}
else if (field is IArrayField arrayField)
{
foreach (var partition in data)
{
if (partition.Value is JsonArray array)
{
for (var i = 0; i < array.Count; i++)
{
if (array[i] is JsonObject arrayItem)
{
foreach (var (key, value) in arrayItem)
{
if (arrayField.FieldsByName.TryGetValue(key, out var nestedField) && ShouldHandle(nestedField, field))
{
Resolve(value);
}
}
}
}
}
}
}
return data;
};
}
return data;
};
public static FieldConverter ResolveInvariant(LanguagesConfig languages)
{
var codeForInvariant = InvariantPartitioning.Key;
var codeForMasterLanguage = languages.Master;
return (data, field) =>
{
if (field.Partitioning.Equals(Partitioning.Invariant))
if (field.Partitioning.Equals(Partitioning.Invariant) && !data.ContainsKey(codeForInvariant))
{
var result = new ContentFieldData();
var result = new ContentFieldData(1);
if (data.TryGetValue(codeForInvariant, out var value))
{
result[codeForInvariant] = value;
}
else if (data.TryGetValue(codeForMasterLanguage, out value))
if (data.TryGetValue(languages.Master, out var value))
{
result[codeForInvariant] = value;
}
@ -202,21 +91,20 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
if (field.Partitioning.Equals(Partitioning.Language))
{
var result = new ContentFieldData();
foreach (var languageCode in languages.AllKeys)
if (data.TryGetValue(codeForInvariant, out var value))
{
if (data.TryGetValue(languageCode, out var value))
{
result[languageCode] = value;
}
else if (languages.IsMaster(languageCode) && data.TryGetValue(codeForInvariant, out value))
var result = new ContentFieldData
{
result[languageCode] = value;
}
[languages.Master] = value
};
return result;
}
return result;
foreach (var key in data.Keys.Where(x => !languages.AllKeys.Contains(x)).ToList())
{
data.Remove(key);
}
}
return data;
@ -231,11 +119,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
foreach (var languageCode in languages.AllKeys)
{
if (!data.TryGetValue(languageCode, out var value))
if (!data.ContainsKey(languageCode))
{
foreach (var fallback in languages.GetPriorities(languageCode))
{
if (data.TryGetValue(fallback, out value))
if (data.TryGetValue(fallback, out var value))
{
data[languageCode] = value;
break;
@ -253,7 +141,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
if (languages?.Any() != true)
{
return (data, field) => data;
return Noop;
}
var languageSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@ -275,145 +163,45 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
if (field.Partitioning.Equals(Partitioning.Language))
{
var result = new ContentFieldData();
foreach (var languageCode in languageSet)
foreach (var (key, _) in data.ToList())
{
if (data.TryGetValue(languageCode, out var value))
if (!languageSet.Contains(key))
{
result[languageCode] = value;
data.Remove(key);
}
}
return result;
}
return data;
};
}
public static FieldConverter ForNestedName2Name(params ValueConverter[] converters)
{
return ForNested(FieldByNameResolver, KeyNameResolver, converters);
}
public static FieldConverter ForNestedName2Id(params ValueConverter[] converters)
{
return ForNested(FieldByNameResolver, KeyIdResolver, converters);
}
public static FieldConverter ForNestedId2Name(params ValueConverter[] converters)
{
return ForNested(FieldByIdResolver, KeyNameResolver, converters);
}
public static FieldConverter ForNestedId2Id(params ValueConverter[] converters)
{
return ForNested(FieldByIdResolver, KeyIdResolver, converters);
}
private static FieldConverter ForNested(FieldResolver fieldResolver, FieldKeyResolver keyResolver, params ValueConverter[] converters)
public static FieldConverter ForValues(params ValueConverter[] converters)
{
return (data, field) =>
{
if (field is IArrayField arrayField)
foreach (var (key, value) in data.ToList())
{
var result = new ContentFieldData();
IJsonValue? newValue = value;
foreach (var (partitionKey, partitionValue) in data)
for (var i = 0; i < converters.Length; i++)
{
if (!(partitionValue is JsonArray array))
{
continue;
}
newValue = converters[i](newValue!, field, null);
var newArray = JsonValue.Array();
foreach (var item in array.OfType<JsonObject>())
if (newValue == null)
{
var newItem = JsonValue.Object();
foreach (var (key, value) in item)
{
var nestedField = fieldResolver(arrayField, key);
if (nestedField == null)
{
continue;
}
var newValue = value;
var isUnset = false;
if (converters != null)
{
foreach (var converter in converters)
{
newValue = converter(newValue, nestedField);
if (ReferenceEquals(newValue, Value.Unset))
{
isUnset = true;
break;
}
}
}
if (!isUnset)
{
newItem.Add(keyResolver(nestedField), newValue);
}
}
newArray.Add(newItem);
break;
}
result.Add(partitionKey, newArray);
}
return result;
}
return data;
};
}
public static FieldConverter ForValues(params ValueConverter[] converters)
{
return (data, field) =>
{
if (!(field is IArrayField))
{
var result = new ContentFieldData();
foreach (var (key, value) in data)
if (newValue == null)
{
var newValue = value;
var isUnset = false;
if (converters != null)
{
foreach (var converter in converters)
{
newValue = converter(newValue, field);
if (ReferenceEquals(newValue, Value.Unset))
{
isUnset = true;
break;
}
}
}
if (!isUnset)
{
result.Add(key, newValue);
}
data.Remove(key);
}
else if (!ReferenceEquals(newValue, value))
{
data[key] = newValue;
}
return result;
}
return data;

53
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldIdentifier.cs

@ -0,0 +1,53 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public abstract class FieldIdentifier
{
public static readonly FieldIdentifier ByName = new FieldByName();
public static readonly FieldIdentifier ById = new FieldById();
public abstract IField? GetField(IArrayField arrayField, string key);
public abstract string GetStringKey(IField field);
private sealed class FieldByName : FieldIdentifier
{
public override IField? GetField(IArrayField arrayField, string key)
{
return arrayField.FieldsByName.GetValueOrDefault(key);
}
public override string GetStringKey(IField field)
{
return field.Name;
}
}
private sealed class FieldById : FieldIdentifier
{
public override IField? GetField(IArrayField arrayField, string key)
{
if (long.TryParse(key, out var id))
{
return arrayField.FieldsById.GetValueOrDefault(id);
}
return null;
}
public override string GetStringKey(IField field)
{
return field.Id.ToString();
}
}
}
}

16
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs

@ -1,16 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public static class Value
{
public static readonly IJsonValue Unset = JsonValue.Create("UNSET");
}
}

142
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -6,6 +6,8 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
@ -14,13 +16,44 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public delegate IJsonValue ValueConverter(IJsonValue value, IField field);
public delegate IJsonValue? ValueConverter(IJsonValue value, IField field, IArrayField? parent = null);
public static class ValueConverters
{
public static readonly ValueConverter Noop = (value, field, parent) => value;
public static readonly ValueConverter ExcludeHidden = (value, field, parent) =>
{
return field.IsForApi() ? value : null;
};
public static readonly ValueConverter ExcludeChangedTypes = (value, field, parent) =>
{
if (value.Type == JsonValueType.Null)
{
return value;
}
try
{
var (_, error) = JsonValueConverter.ConvertValue(field, value);
if (error != null)
{
return null;
}
}
catch
{
return null;
}
return value;
};
public static ValueConverter DecodeJson(IJsonSerializer jsonSerializer)
{
return (value, field) =>
return (value, field, parent) =>
{
if (field is IField<JsonFieldProperties> && value is JsonScalar<string> s)
{
@ -35,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static ValueConverter EncodeJson(IJsonSerializer jsonSerializer)
{
return (value, field) =>
return (value, field, parent) =>
{
if (value.Type != JsonValueType.Null && field is IField<JsonFieldProperties>)
{
@ -48,32 +81,103 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
};
}
public static ValueConverter ExcludeHidden()
public static ValueConverter ResolveAssetUrls(IReadOnlyCollection<string>? fields, IUrlGenerator urlGenerator)
{
return (value, field) => !field.IsForApi() ? Value.Unset : value;
}
if (fields?.Any() != true)
{
return Noop;
}
public static ValueConverter ExcludeChangedTypes()
{
return (value, field) =>
Func<IField, IField?, bool> shouldHandle;
if (fields.Contains("*"))
{
if (value.Type == JsonValueType.Null)
{
return value;
}
shouldHandle = (field, parent) => true;
}
else
{
var paths = fields.Select(x => x.Split('.')).ToList();
try
shouldHandle = (field, parent) =>
{
var (_, error) = JsonValueConverter.ConvertValue(field, value);
for (var i = 0; i < paths.Count; i++)
{
var path = paths[i];
if (parent != null)
{
return path.Length == 2 && path[0] == parent.Name && path[1] == field.Name;
}
else
{
return path.Length == 1 && path[0] == field.Name;
}
}
if (error != null)
return false;
};
}
return (value, field, parent) =>
{
if (value is JsonArray array && shouldHandle(field, parent))
{
for (var i = 0; i < array.Count; i++)
{
return Value.Unset;
var id = array[i].ToString();
array[i] = JsonValue.Create(urlGenerator.AssetContent(Guid.Parse(id)));
}
}
catch
return value;
};
}
public static ValueConverter ForNested(params ValueConverter[] converters)
{
if (converters?.Any() != true)
{
return Noop;
}
return (value, field, parent) =>
{
if (value is JsonArray array && field is IArrayField arrayField)
{
return Value.Unset;
foreach (var nested in array.OfType<JsonObject>())
{
foreach (var (fieldName, nestedValue) in nested.ToList())
{
IJsonValue? newValue = nestedValue;
if (arrayField.FieldsByName.TryGetValue(fieldName, out var nestedField))
{
for (var i = 0; i < converters.Length; i++)
{
newValue = converters[i](newValue!, nestedField, arrayField);
if (newValue == null)
{
break;
}
}
}
else
{
newValue = null;
}
if (newValue == null)
{
nested.Remove(fieldName);
}
else if (!ReferenceEquals(nestedValue, newValue))
{
nested[fieldName] = newValue;
}
}
}
}
return value;

7
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -89,12 +89,17 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
if (value is JsonArray array)
{
var result = new JsonArray(array);
var result = array;
for (var i = 0; i < result.Count; i++)
{
if (!IsValidReference(result[i]))
{
if (ReferenceEquals(result, array))
{
result = new JsonArray(array);
}
result.RemoveAt(i);
i--;
}

6
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs

@ -18,16 +18,16 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
if (validIds == null)
{
return (value, field) => value;
return ValueConverters.Noop;
}
var cleaner = new ReferencesCleaner(validIds);
return (value, field) =>
return (value, field, parent) =>
{
if (value.Type == JsonValueType.Null)
{
return value!;
return value;
}
cleaner.SetValue(value);

16
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/DataConverter.cs

@ -19,20 +19,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
public DataConverter(IJsonSerializer serializer)
{
var decoder = ValueConverters.DecodeJson(serializer);
decodeJsonConverters = new[]
{
FieldConverters.ForValues(
ValueConverters.DecodeJson(serializer)),
FieldConverters.ForNestedId2Name(
ValueConverters.DecodeJson(serializer))
FieldConverters.ForValues(decoder, ValueConverters.ForNested(decoder))
};
var encoder = ValueConverters.EncodeJson(serializer);
encodeJsonConverters = new[]
{
FieldConverters.ForValues(
ValueConverters.EncodeJson(serializer)),
FieldConverters.ForNestedName2Id(
ValueConverters.EncodeJson(serializer))
FieldConverters.ForValues(encoder, ValueConverters.ForNested(encoder))
};
}
@ -43,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
public IdContentData ToMongoModel(NamedContentData result, Schema schema)
{
return result.ConvertName2Id(schema, encodeJsonConverters);
return result.ConvertName2IdCloned(schema, encodeJsonConverters);
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (context.PlainResult is IContentEntity content && NotEnriched(context))
{
var enriched = await contentEnricher.EnrichAsync(content, contextProvider.Context);
var enriched = await contentEnricher.EnrichAsync(content, true, contextProvider.Context);
context.Complete(enriched);
}

16
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -36,20 +36,25 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
this.contentQuery = contentQuery;
}
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context)
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, bool cloneData, Context context)
{
Guard.NotNull(content);
var enriched = await EnrichAsync(Enumerable.Repeat(content, 1), context);
var enriched = await EnrichInternalAsync(Enumerable.Repeat(content, 1), cloneData, context);
return enriched[0];
}
public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context)
public Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context)
{
Guard.NotNull(contents);
Guard.NotNull(context);
return EnrichInternalAsync(contents, false, context);
}
private async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichInternalAsync(IEnumerable<IContentEntity> contents, bool cloneData, Context context)
{
using (Profiler.TraceMethod<ContentEnricher>())
{
var results = new List<ContentEntity>();
@ -65,6 +70,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var result = SimpleMapper.Map(content, new ContentEntity());
if (cloneData)
{
result.Data = result.Data.Clone();
}
results.Add(result);
}

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs

@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public interface IContentEnricher
{
Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context);
Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, bool cloneData, Context context);
Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context);
}

19
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs

@ -104,17 +104,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
if (!context.IsFrontendClient)
{
yield return FieldConverters.ExcludeHidden();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden());
yield return FieldConverters.ExcludeHidden;
yield return FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeHidden));
}
yield return FieldConverters.ExcludeChangedTypes();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes());
yield return FieldConverters.ExcludeChangedTypes;
yield return FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeChangedTypes));
if (cleanReferences != null)
{
yield return FieldConverters.ForValues(cleanReferences);
yield return FieldConverters.ForNestedName2Name(cleanReferences);
yield return FieldConverters.ForValues(ValueConverters.ForNested(cleanReferences));
}
yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig);
@ -134,11 +134,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, languages);
}
var assetUrls = context.AssetUrls();
var assetUrls = context.AssetUrls().ToList();
if (assetUrls.Any())
if (assetUrls.Count > 0)
{
yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), urlGenerator);
var resolveAssetUrls = ValueConverters.ResolveAssetUrls(assetUrls, urlGenerator);
yield return FieldConverters.ForValues(resolveAssetUrls);
yield return FieldConverters.ForValues(ValueConverters.ForNested(resolveAssetUrls));
}
}
}

2
backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs

@ -16,6 +16,8 @@ namespace Squidex.Infrastructure.Json.Objects
bool TryGet(string pathSegment, [MaybeNullWhen(false)] out IJsonValue result);
IJsonValue Clone();
string ToJsonString();
string ToString();

10
backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs

@ -26,6 +26,11 @@ namespace Squidex.Infrastructure.Json.Objects
}
public JsonArray(JsonArray source)
: base(source.ToList())
{
}
private JsonArray(List<IJsonValue> source)
: base(source)
{
}
@ -90,6 +95,11 @@ namespace Squidex.Infrastructure.Json.Objects
return hashCode;
}
public IJsonValue Clone()
{
return new JsonArray(this.Select(x => x.Clone()).ToList());
}
public string ToJsonString()
{
return ToString();

5
backend/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs

@ -43,6 +43,11 @@ namespace Squidex.Infrastructure.Json.Objects
return 0;
}
public IJsonValue Clone()
{
return this;
}
public string ToJsonString()
{
return ToString();

10
backend/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs

@ -61,6 +61,11 @@ namespace Squidex.Infrastructure.Json.Objects
inner = new Dictionary<string, IJsonValue>(obj.inner);
}
private JsonObject(Dictionary<string, IJsonValue> source)
{
inner = source;
}
public JsonObject Add(string key, object? value)
{
return Add(key, JsonValue.Create(value));
@ -123,6 +128,11 @@ namespace Squidex.Infrastructure.Json.Objects
return inner.DictionaryHashCode();
}
public IJsonValue Clone()
{
return new JsonObject(this.ToDictionary(x => x.Key, x => x.Value.Clone()));
}
public string ToJsonString()
{
return ToString();

5
backend/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs

@ -36,6 +36,11 @@ namespace Squidex.Infrastructure.Json.Objects
return other != null && other.Type == Type && Equals(other.Value, Value);
}
public IJsonValue Clone()
{
return this;
}
public override int GetHashCode()
{
return Value.GetHashCode();

19
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs

@ -231,5 +231,24 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
Assert.True(lhs.Equals((object)rhs));
Assert.Equal(lhs.GetHashCode(), rhs.GetHashCode());
}
[Fact]
public void Should_clone_named_value_and_also_children()
{
var source = new NamedContentData
{
["field1"] = new ContentFieldData(),
["field2"] = new ContentFieldData()
};
var clone = source.Clone();
Assert.NotSame(source, clone);
foreach (var (key, value) in clone)
{
Assert.NotSame(value, source[key]);
}
}
}
}

20
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs

@ -9,6 +9,7 @@ using System;
using System.Linq;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Contents
@ -62,5 +63,24 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
Assert.Null(string.IsInterned(serialized.Keys.First()));
}
[Fact]
public void Should_clone_value_and_also_children()
{
var source = new ContentFieldData
{
["en"] = JsonValue.Array(),
["de"] = JsonValue.Array()
};
var clone = source.Clone();
Assert.NotSame(source, clone);
foreach (var (key, value) in clone)
{
Assert.NotSame(value, source[key]);
}
}
}
}

74
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs

@ -8,6 +8,7 @@
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
@ -25,7 +26,12 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddNumber(3, "field3", Partitioning.Invariant)
.AddAssets(5, "assets1", Partitioning.Invariant)
.AddAssets(6, "assets2", Partitioning.Invariant)
.AddArray(7, "array", Partitioning.Invariant, h => h
.AddNumber(71, "nested1")
.AddNumber(72, "nested2"))
.AddJson(4, "json", Partitioning.Language)
.HideField(2)
.HideField(71, 7)
.UpdateField(3, f => f.Hide());
}
@ -40,17 +46,34 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 1))
.AddField("array",
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("nested1", 100)
.Add("nested2", 200)
.Add("invalid", 300))))
.AddField("invalid",
new ContentFieldData()
.AddValue("iv", 2));
var actual = input.ConvertName2Id(schema, (data, field) => field.Name == "field2" ? null : data);
var hideRoot = FieldConverters.ExcludeHidden;
var hideNested = FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeHidden));
var actual = input.ConvertName2IdCloned(schema, hideRoot, hideNested);
var expected =
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("en", "EN"));
.AddValue("en", "EN"))
.AddField(7,
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("72", 200))));
Assert.Equal(expected, actual);
}
@ -81,32 +104,6 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_id_to_id()
{
var input =
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("en", "EN"))
.AddField(2,
new ContentFieldData()
.AddValue("iv", 1))
.AddField(99,
new ContentFieldData()
.AddValue("iv", 2));
var actual = input.ConvertId2Id(schema, (data, field) => field.Name == "field2" ? null : data);
var expected =
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("en", "EN"));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_id_to_name()
{
@ -118,17 +115,34 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddField(2,
new ContentFieldData()
.AddValue("iv", 1))
.AddField(7,
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("71", 100)
.Add("72", 200)
.Add("799", 300))))
.AddField(99,
new ContentFieldData()
.AddValue("iv", 2));
var actual = input.ConvertId2Name(schema, (data, field) => field.Name == "field2" ? null : data);
var hideRoot = FieldConverters.ExcludeHidden;
var hideNested = FieldConverters.ForValues(ValueConverters.ForNested(ValueConverters.ExcludeHidden));
var actual = input.ConvertId2Name(schema, hideRoot, hideNested);
var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "EN"));
.AddValue("en", "EN"))
.AddField("array",
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("nested2", 200))));
Assert.Equal(expected, actual);
}

273
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs

@ -5,10 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
@ -21,31 +18,22 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{
public class FieldConvertersTests
{
private readonly IUrlGenerator urlGenerato = A.Fake<IUrlGenerator>();
private readonly Guid id1 = Guid.NewGuid();
private readonly Guid id2 = Guid.NewGuid();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE);
public FieldConvertersTests()
{
A.CallTo(() => urlGenerato.AssetContent(A<Guid>._))
.ReturnsLazily(ctx => $"url/to/{ctx.GetArgument<Guid>(0)}");
}
[Fact]
public void Should_filter_for_value_conversion()
{
var field = Fields.String(1, "string", Partitioning.Invariant);
var input =
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Object());
var actual = FieldConverters.ForValues((f, i) => Value.Unset)(input, field);
var result = FieldConverters.ForValues((value, field, parent) => null)(source, field);
var expected = new ContentFieldData();
Assert.Equal(expected, actual);
Assert.Equal(expected, result);
}
[Fact]
@ -53,133 +41,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{
var field = Fields.Json(1, "json", Partitioning.Invariant);
var input =
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Object());
var actual = FieldConverters.ForValues(ValueConverters.EncodeJson(TestUtils.DefaultSerializer))(input, field);
var result = FieldConverters.ForValues(ValueConverters.EncodeJson(TestUtils.DefaultSerializer))(source, field);
var expected =
new ContentFieldData()
.AddValue("iv", "e30=");
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_name_to_id()
{
var field =
Fields.Array(1, "1", Partitioning.Invariant,
Fields.Number(1, "field1"),
Fields.Number(2, "field2").Hide());
var input =
new ContentFieldData()
.AddJsonValue(
JsonValue.Array(
JsonValue.Object()
.Add("field1", 100)
.Add("field2", 200)
.Add("invalid", 300)));
var actual = FieldConverters.ForNestedName2Id(ValueConverters.ExcludeHidden())(input, field);
var expected =
new ContentFieldData()
.AddJsonValue(
JsonValue.Array(
JsonValue.Object()
.Add("1", 100)));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_name_to_name()
{
var field =
Fields.Array(1, "1", Partitioning.Invariant,
Fields.Number(1, "field1"),
Fields.Number(2, "field2").Hide());
var input =
new ContentFieldData()
.AddJsonValue(
JsonValue.Array(
JsonValue.Object()
.Add("field1", 100)
.Add("field2", 200)
.Add("invalid", 300)));
var actual = FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden())(input, field);
var expected =
new ContentFieldData()
.AddJsonValue(
JsonValue.Array(
JsonValue.Object()
.Add("field1", 100)));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_id_to_id()
{
var field =
Fields.Array(1, "1", Partitioning.Invariant,
Fields.Number(1, "field1"),
Fields.Number(2, "field2").Hide());
var input =
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("1", 100)
.Add("2", 200)
.Add("99", 300)));
var actual = FieldConverters.ForNestedId2Id(ValueConverters.ExcludeHidden())(input, field);
var expected =
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("1", 100)));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_id_to_name()
{
var field =
Fields.Array(1, "1", Partitioning.Invariant,
Fields.Number(1, "field1"),
Fields.Number(2, "field2").Hide());
var input =
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("1", 100)
.Add("2", 200)
.Add("99", 300)));
var actual = FieldConverters.ForNestedId2Name(ValueConverters.ExcludeHidden())(input, field);
var expected =
new ContentFieldData()
.AddValue("iv",
JsonValue.Array(
JsonValue.Object()
.Add("field1", 100)));
Assert.Equal(expected, actual);
Assert.Equal(expected, result);
}
[Fact]
@ -192,7 +64,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddValue("en", null)
.AddValue("de", 1);
var result = FieldConverters.ExcludeChangedTypes()(source, field);
var result = FieldConverters.ExcludeChangedTypes(source, field);
Assert.Same(source, result);
}
@ -207,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddValue("en", "EN")
.AddValue("de", 0);
var result = FieldConverters.ExcludeChangedTypes()(source, field);
var result = FieldConverters.ExcludeChangedTypes(source, field);
Assert.Null(result);
}
@ -219,19 +91,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source = new ContentFieldData();
var result = FieldConverters.ExcludeHidden()(source, field);
var result = FieldConverters.ExcludeHidden(source, field);
Assert.Same(source, result);
}
[Fact]
public void Should_return_null_values_if_field_hidden()
public void Should_return_null_if_field_hidden()
{
var field = Fields.String(1, "string", Partitioning.Language);
var source = new ContentFieldData();
var result = FieldConverters.ExcludeHidden()(source, field.Hide());
var result = FieldConverters.ExcludeHidden(source, field.Hide());
Assert.Null(result);
}
@ -293,8 +165,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source =
new ContentFieldData()
.AddValue("iv", "A")
.AddValue("it", "B");
.AddValue("iv", "A");
var expected =
new ContentFieldData()
@ -474,125 +345,5 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
Assert.Same(source, result);
}
[Fact]
public void Should_convert_asset_ids_to_urls()
{
var field = Fields.Assets(1, "assets", Partitioning.Invariant);
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(id1, id2));
var expected =
new ContentFieldData()
.AddJsonValue(JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "assets" }), urlGenerato)(source, field);
Assert.Equal(expected, result);
}
[Fact]
public void Should_convert_nested_asset_ids_to_urls()
{
var field =
Fields.Array(1, "array", Partitioning.Invariant,
Fields.Assets(1, "assets"));
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(
JsonValue.Object()
.Add("assets", JsonValue.Array(id1, id2))));
var expected =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(
JsonValue.Object()
.Add("assets", JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"))));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "array.assets" }), urlGenerato)(source, field);
Assert.Equal(expected, result);
}
[Fact]
public void Should_convert_asset_ids_to_urls_for_wildcard_fields()
{
var field = Fields.Assets(1, "assets", Partitioning.Invariant);
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(id1, id2));
var expected =
new ContentFieldData()
.AddJsonValue(JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "*" }), urlGenerato)(source, field);
Assert.Equal(expected, result);
}
[Fact]
public void Should_convert_nested_asset_ids_to_urls_for_wildcard_fields()
{
var field =
Fields.Array(1, "array", Partitioning.Invariant,
Fields.Assets(1, "assets"));
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(
JsonValue.Object()
.Add("assets", JsonValue.Array(id1, id2))));
var expected =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(
JsonValue.Object()
.Add("assets", JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"))));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "*" }), urlGenerato)(source, field);
Assert.Equal(expected, result);
}
[Fact]
public void Should_not_convert_asset_ids_to_urls_when_field_does_not_match()
{
var field = Fields.Assets(1, "assets", Partitioning.Invariant);
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(id1, id2));
var expected =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(id1, id2));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "other" }), urlGenerato)(source, field);
Assert.Equal(expected, result);
}
[Fact]
public void Should_not_convert_asset_ids_to_urls_when_fields_is_null()
{
var field = Fields.Assets(1, "assets", Partitioning.Invariant);
var source =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(id1, id2));
var expected =
new ContentFieldData()
.AddJsonValue(JsonValue.Array(id1, id2));
var result = FieldConverters.ResolveAssetUrls(null, urlGenerato)(source, field);
Assert.Equal(expected, result);
}
}
}

90
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs

@ -5,8 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using FakeItEasy;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
@ -14,10 +17,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{
public class ValueConvertersTests
{
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly Guid id1 = Guid.NewGuid();
private readonly Guid id2 = Guid.NewGuid();
private readonly RootField<StringFieldProperties> stringField = Fields.String(1, "1", Partitioning.Invariant);
private readonly RootField<JsonFieldProperties> jsonField = Fields.Json(1, "1", Partitioning.Invariant);
private readonly RootField<NumberFieldProperties> numberField = Fields.Number(1, "1", Partitioning.Invariant);
public ValueConvertersTests()
{
A.CallTo(() => urlGenerator.AssetContent(A<Guid>._))
.ReturnsLazily(ctx => $"url/to/{ctx.GetArgument<Guid>(0)}");
}
[Fact]
public void Should_encode_json_value()
{
@ -79,23 +91,89 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
}
[Fact]
public void Should_return_unset_if_field_hidden()
public void Should_return_null_if_field_hidden()
{
var source = JsonValue.Create(123);
var result = ValueConverters.ExcludeHidden()(source, stringField.Hide());
var result = ValueConverters.ExcludeHidden(source, stringField.Hide());
Assert.Same(Value.Unset, result);
Assert.Null(result);
}
[Fact]
public void Should_return_unset_if_field_has_wrong_type()
public void Should_return_null_if_field_has_wrong_type()
{
var source = JsonValue.Create("invalid");
var result = ValueConverters.ExcludeChangedTypes()(source, numberField);
var result = ValueConverters.ExcludeChangedTypes(source, numberField);
Assert.Null(result);
}
[Theory]
[InlineData("assets")]
[InlineData("*")]
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 expected = JsonValue.Array($"url/to/{id1}", $"url/to/{id2}");
var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("other")]
[InlineData("**")]
public void Should_not_convert_asset_ids_when_field_name_does_not_match(string path)
{
var field = Fields.Assets(1, "assets", Partitioning.Invariant);
var source = JsonValue.Array(id1, id2);
var expected = source;
var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("parent.assets")]
[InlineData("*")]
public void Should_convert_nested_asset_ids_to_urls(string path)
{
var field = Fields.Array(1, "parent", Partitioning.Invariant, Fields.Assets(11, "assets"));
var source = JsonValue.Array(id1, id2);
var expected = JsonValue.Array($"url/to/{id1}", $"url/to/{id2}");
var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field.Fields[0], field);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("assets")]
[InlineData("parent")]
[InlineData("parent.other")]
[InlineData("other.assets")]
public void Should_not_convert_nested_asset_ids_when_field_name_does_not_match(string path)
{
var field = Fields.Array(1, "parent", Partitioning.Invariant, Fields.Assets(11, "assets"));
var source = JsonValue.Array(id1, id2);
var expected = source;
var result = ValueConverters.ResolveAssetUrls(HashSet.Of(path), urlGenerator)(source, field.Fields[0], field);
Assert.Same(Value.Unset, result);
Assert.Equal(expected, result);
}
}
}

10
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs

@ -109,11 +109,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
.Add("nested", JsonValue.Array(id2)))));
var cleaner = ValueReferencesConverter.CleanReferences(new HashSet<Guid> { id2 });
var cleanNested = ValueConverters.ForNested(cleaner);
var converter = FieldConverters.ForValues(cleaner);
var converterNested = FieldConverters.ForNestedName2Name(cleaner);
var converter = FieldConverters.ForValues(cleaner, cleanNested);
var actual = source.ConvertName2Name(schema, converter, converterNested);
var actual = source.ConvertName2Name(schema, converter);
Assert.Equal(expected, actual);
}
@ -210,7 +210,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))]
public void Should_return_same_value_from_field_when_value_is_json_null(IField field)
{
var result = ValueReferencesConverter.CleanReferences(RandomIds())(JsonValue.Null, field);
var result = ValueReferencesConverter.CleanReferences(RandomIds())(JsonValue.Null, field, null);
Assert.Equal(JsonValue.Null, result);
}
@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var value = CreateValue(id1, id2);
var result = ValueReferencesConverter.CleanReferences(HashSet.Of(id1))(value, field);
var result = ValueReferencesConverter.CleanReferences(HashSet.Of(id1))(value, field, null);
Assert.Equal(CreateValue(id1), result);
}

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await sut.HandleAsync(context);
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, requestContext))
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, A<bool>._, requestContext))
.MustNotHaveHappened();
}
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Same(result, context.Result<IEnrichedContentEntity>());
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, requestContext))
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, A<bool>._, requestContext))
.MustNotHaveHappened();
}
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(result, requestContext))
A.CallTo(() => contentEnricher.EnrichAsync(result, true, requestContext))
.Returns(enriched);
await sut.HandleAsync(context);

33
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
@ -83,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(source, false, requestContext);
A.CallTo(() => step1.EnrichAsync(requestContext))
.MustHaveHappened();
@ -108,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(source, false, requestContext);
Assert.Same(schema, step1.Schema);
Assert.Same(schema, step1.Schema);
@ -117,9 +118,33 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
.MustHaveHappenedOnceExactly();
}
private ContentEntity CreateContent()
[Fact]
public async Task Should_clone_data_when_requested()
{
var source = CreateContent(new NamedContentData());
var sut = new ContentEnricher(Enumerable.Empty<IContentEnricherStep>(), new Lazy<IContentQueryService>(() => contentQuery));
var result = await sut.EnrichAsync(source, true, requestContext);
Assert.NotSame(source.Data, result.Data);
}
[Fact]
public async Task Should_not_clone_data_when_not_requested()
{
var source = CreateContent(new NamedContentData());
var sut = new ContentEnricher(Enumerable.Empty<IContentEnricherStep>(), new Lazy<IContentQueryService>(() => contentQuery));
var result = await sut.EnrichAsync(source, false, requestContext);
Assert.Same(source.Data, result.Data);
}
private ContentEntity CreateContent(NamedContentData? data = null)
{
return new ContentEntity { SchemaId = schemaId };
return new ContentEntity { SchemaId = schemaId, Data = data! };
}
}
}

86
backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs

@ -182,6 +182,22 @@ namespace Squidex.Infrastructure.Json.Objects
Assert.Equal("[1, \"2\"]", json.ToString());
}
[Fact]
public void Should_create_array_from_source()
{
var json = JsonValue.Array(1, "2");
var copy = new JsonArray(json);
Assert.Equal("[1, \"2\"]", copy.ToJsonString());
Assert.Equal("[1, \"2\"]", copy.ToString());
copy.Clear();
Assert.Empty(copy);
Assert.NotEmpty(json);
}
[Fact]
public void Should_create_object()
{
@ -236,6 +252,76 @@ namespace Squidex.Infrastructure.Json.Objects
Assert.Equal("null", json.ToString());
}
[Fact]
public void Should_clone_number_and_return_same()
{
var source = JsonValue.Create(1);
var clone = source.Clone();
Assert.Same(source, clone);
}
[Fact]
public void Should_clone_string_and_return_same()
{
var source = JsonValue.Create("test");
var clone = source.Clone();
Assert.Same(source, clone);
}
[Fact]
public void Should_clone_boolean_and_return_same()
{
var source = JsonValue.Create(true);
var clone = source.Clone();
Assert.Same(source, clone);
}
[Fact]
public void Should_clone_null_and_return_same()
{
var source = JsonValue.Null;
var clone = source.Clone();
Assert.Same(source, clone);
}
[Fact]
public void Should_clone_array_and_also_children()
{
var source = JsonValue.Array(JsonValue.Array(), JsonValue.Array());
var clone = (JsonArray)source.Clone();
Assert.NotSame(source, clone);
for (var i = 0; i < source.Count; i++)
{
Assert.NotSame(clone[i], source[i]);
}
}
[Fact]
public void Should_clone_object_and_also_children()
{
var source = JsonValue.Object().Add("1", JsonValue.Array()).Add("2", JsonValue.Array());
var clone = (JsonObject)source.Clone();
Assert.NotSame(source, clone);
foreach (var (key, value) in clone)
{
Assert.NotSame(value, source[key]);
}
}
[Fact]
public void Should_create_arrays_in_different_ways()
{

Loading…
Cancel
Save