Browse Source

* Tests for array fields

* Conversions.
pull/297/head
Sebastian 8 years ago
parent
commit
a8a08884e8
  1. 6
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  2. 4
      src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs
  3. 4
      src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs
  4. 114
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  5. 124
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  6. 20
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs
  7. 77
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  8. 13
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs
  9. 96
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  10. 69
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs
  11. 101
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  12. 4
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs
  13. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  14. 2
      src/Squidex.Infrastructure.Redis/RedisPubSub.cs
  15. 12
      src/Squidex.Infrastructure/CollectionExtensions.cs
  16. 2
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs
  17. 2
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs
  18. 222
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs
  19. 75
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
  20. 10
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs

6
src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs

@ -25,8 +25,8 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
} }
protected ContentData(IDictionary<T, ContentFieldData> copy, IEqualityComparer<T> comparer) protected ContentData(int capacity, IEqualityComparer<T> comparer)
: base(copy, comparer) : base(capacity, comparer)
{ {
} }
@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
foreach (var otherValue in source) foreach (var otherValue in source)
{ {
var fieldValue = target.GetOrAdd(otherValue.Key, x => new ContentFieldData()); var fieldValue = target.GetOrAddNew(otherValue.Key);
foreach (var value in otherValue.Value) foreach (var value in otherValue.Value)
{ {

4
src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs

@ -18,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
} }
public IdContentData(IdContentData copy) public IdContentData(int capacity)
: base(copy, EqualityComparer<long>.Default) : base(capacity, EqualityComparer<long>.Default)
{ {
} }

4
src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs

@ -18,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
} }
public NamedContentData(NamedContentData copy) public NamedContentData(int capacity)
: base(copy, EqualityComparer<string>.Default) : base(capacity, EqualityComparer<string>.Default)
{ {
} }

114
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -5,6 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -13,120 +16,83 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
public delegate ContentFieldData FieldConverter(ContentFieldData data, IRootField field); public delegate ContentFieldData FieldConverter(ContentFieldData data, IRootField field);
public delegate JToken ValueConverter(JToken value, IRootField field);
public static class ContentConverter public static class ContentConverter
{ {
public static NamedContentData ToNameModel(this IdContentData source, Schema schema, params FieldConverter[] converters) public static NamedContentData ToNameModel(this IdContentData content, Schema schema, params FieldConverter[] converters)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var result = new NamedContentData(); var result = new NamedContentData(content.Count);
foreach (var fieldValue in source)
{
if (!schema.FieldsById.TryGetValue(fieldValue.Key, out var field))
{
continue;
}
var fieldData = Convert(fieldValue.Value, field, converters);
if (fieldData != null)
{
result[field.Name] = fieldData;
}
}
return result; return ConvertInternal(content, result, schema.FieldsById, x => x.Name, converters);
} }
public static IdContentData ToIdModel(this NamedContentData content, Schema schema, params FieldConverter[] converters) public static IdContentData ToIdModel(this NamedContentData content, Schema schema, params FieldConverter[] converters)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var result = new IdContentData(); var result = new IdContentData(content.Count);
foreach (var fieldValue in content) return ConvertInternal(content, result, schema.FieldsByName, x => x.Id, converters);
{
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field))
{
continue;
}
var fieldData = Convert(fieldValue.Value, field, converters);
if (fieldData != null)
{
result[field.Id] = fieldData;
}
}
return result;
} }
public static IdContentData Convert(this IdContentData content, Schema schema, params FieldConverter[] converters) public static IdContentData Convert(this IdContentData content, Schema schema, params FieldConverter[] converters)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var result = new IdContentData(); var result = new IdContentData(content.Count);
foreach (var fieldValue in content) return ConvertInternal(content, result, schema.FieldsById, x => x.Id, converters);
{
if (!schema.FieldsById.TryGetValue(fieldValue.Key, out var field))
{
continue;
}
var fieldData = Convert(fieldValue.Value, field, converters);
if (fieldData != null)
{
result[field.Id] = fieldData;
}
}
return result;
} }
public static NamedContentData Convert(this NamedContentData content, Schema schema, params FieldConverter[] converters) public static NamedContentData Convert(this NamedContentData content, Schema schema, params FieldConverter[] converters)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var result = new NamedContentData(); var result = new NamedContentData(content.Count);
foreach (var fieldValue in content) return ConvertInternal(content, result, schema.FieldsByName, x => x.Name, converters);
}
private static TDict2 ConvertInternal<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>
{
foreach (var fieldKvp in source)
{ {
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field)) if (!fields.TryGetValue(fieldKvp.Key, out var field))
{ {
continue; continue;
} }
var fieldData = Convert(fieldValue.Value, field, converters); var fieldValue = fieldKvp.Value;
if (fieldData != null) if (converters != null)
{ {
result[field.Name] = fieldData; foreach (var converter in converters)
} {
} fieldValue = converter(fieldValue, field);
return result; if (fieldValue == null)
} {
break;
}
}
}
private static ContentFieldData Convert(ContentFieldData fieldData, IRootField field, FieldConverter[] converters) if (fieldValue != null)
{
if (converters != null)
{
foreach (var converter in converters)
{ {
fieldData = converter(fieldData, field); target.Add(targetKey(field), fieldValue);
if (fieldData == null)
{
break;
}
} }
} }
return fieldData; return target;
} }
} }
} }

124
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -35,29 +34,23 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
return (data, field) => return (data, field) =>
{ {
var isValid = true;
foreach (var value in data.Values) foreach (var value in data.Values)
{ {
if (value.IsNull())
{
continue;
}
try try
{ {
if (!value.IsNull()) JsonValueConverter.ConvertValue(field, value);
{
JsonValueConverter.ConvertValue(field, value);
}
} }
catch catch
{ {
isValid = false; return null;
break;
} }
} }
if (!isValid)
{
return null;
}
return data; return data;
}; };
} }
@ -173,12 +166,10 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return (data, field) => data; return (data, field) => data;
} }
var languageCodes = var languageCodes = languages.Select(x => x.Iso2Code).Where(x => languagesConfig.Contains(x));
new HashSet<string>( var languageSet = new HashSet<string>(languageCodes, StringComparer.OrdinalIgnoreCase);
languages.Select(x => x.Iso2Code).Where(x => languagesConfig.Contains(x)),
StringComparer.OrdinalIgnoreCase);
if (languageCodes.Count == 0) if (languageSet.Count == 0)
{ {
return (data, field) => data; return (data, field) => data;
} }
@ -189,7 +180,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
var result = new ContentFieldData(); var result = new ContentFieldData();
foreach (var languageCode in languageCodes) foreach (var languageCode in languageSet)
{ {
if (data.TryGetValue(languageCode, out var value)) if (data.TryGetValue(languageCode, out var value))
{ {
@ -204,58 +195,101 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
}; };
} }
public static FieldConverter DecodeJson() private static FieldConverter ForNested(params ValueConverter[] converters)
{ {
return (data, field) => return (data, field) =>
{ {
if (field is IField<JsonFieldProperties>) if (field is IArrayField arrayField)
{ {
var result = new ContentFieldData(); foreach (var partition in data)
foreach (var partitionValue in data)
{ {
if (partitionValue.Value.IsNull()) if (partition.Value is JArray jArray)
{
result[partitionValue.Key] = null;
}
else
{ {
var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString())); for (var i = 0; i < jArray.Count; i++)
{
result[partitionValue.Key] = JToken.Parse(value); if (jArray[i] is JObject item)
{
var result = new JObject();
foreach (var kvp in item)
{
if (!arrayField.FieldsByName.TryGetValue(kvp.Key, out var nestedField))
{
continue;
}
var newValue = kvp.Value;
if (converters != null)
{
foreach (var converter in converters)
{
newValue = converter(newValue, field);
if (ReferenceEquals(newValue, Value.Unset))
{
break;
}
}
}
if (!ReferenceEquals(newValue, Value.Unset))
{
result.Add(field.Id.ToString(), newValue);
}
}
jArray[i] = item;
}
}
} }
} }
return result;
} }
return data; return data;
}; };
} }
public static FieldConverter EncodeJson() public static FieldConverter ForValues(params ValueConverter[] converters)
{ {
return (data, field) => return (data, field) =>
{ {
if (field is IField<JsonFieldProperties>) if (!(field is IArrayField))
{ {
var result = new ContentFieldData(); ContentFieldData result = null;
foreach (var partitionValue in data) foreach (var partition in data)
{ {
if (partitionValue.Value.IsNull()) var newValue = partition.Value;
if (converters != null)
{ {
result[partitionValue.Key] = null; foreach (var converter in converters)
{
newValue = converter(newValue, field);
if (ReferenceEquals(newValue, Value.Unset))
{
break;
}
}
} }
else
if (result != null || !ReferenceEquals(newValue, partition.Value))
{ {
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString())); if (result == null)
{
result = new ContentFieldData();
}
result[partitionValue.Key] = value; if (!ReferenceEquals(newValue, Value.Unset))
{
result.Add(partition.Key, newValue);
}
} }
} }
return result; return result ?? data;
} }
return data; return data;

20
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public sealed class Value
{
public static readonly JToken Unset = JValue.CreateUndefined();
private Value()
{
}
}
}

77
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -0,0 +1,77 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public static class ValueConverters
{
public static ValueConverter DecodeJson()
{
return (value, field) =>
{
if (!value.IsNull() && field is IField<JsonFieldProperties>)
{
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value.ToString()));
return JToken.Parse(decoded);
}
return value;
};
}
public static ValueConverter EncodeJson()
{
return (value, field) =>
{
if (!value.IsNull() && field is IField<JsonFieldProperties>)
{
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString()));
return encoded;
}
return value;
};
}
public static ValueConverter ExcludeHidden()
{
return (value, field) =>
{
return field.IsHidden ? null : value;
};
}
public static ValueConverter ExcludeChangedTypes()
{
return (value, field) =>
{
try
{
if (!value.IsNull())
{
JsonValueConverter.ConvertValue(field, value);
}
}
catch
{
return null;
}
return value;
};
}
}
}

13
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
@ -15,20 +14,18 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class FieldReferencesConverter public static class FieldReferencesConverter
{ {
public static FieldConverter CleanReferences(IEnumerable<Guid> deletedReferencedIds) public static ValueConverter CleanReferences(IEnumerable<Guid> deletedReferencedIds)
{ {
var ids = new HashSet<Guid>(deletedReferencedIds); var ids = new HashSet<Guid>(deletedReferencedIds);
return (data, field) => return (value, field) =>
{ {
foreach (var partitionValue in data.Where(x => !x.Value.IsNull()).ToList()) if (value.IsNull())
{ {
var newValue = field.CleanReferences(partitionValue.Value, ids); return value;
data[partitionValue.Key] = newValue;
} }
return data; return field.CleanReferences(value, ids);
}; };
} }
} }

96
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -7,65 +7,95 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ReferencesCleaner public sealed class ReferencesCleaner : IFieldVisitor<JToken>
{ {
private static readonly List<Guid> EmptyIds = new List<Guid>(); private readonly JToken value;
private readonly ICollection<Guid> oldReferences;
public static JToken CleanReferences(this IField field, JToken value, ISet<Guid> oldReferences) private ReferencesCleaner(JToken value, ICollection<Guid> oldReferences)
{ {
if ((field is IField<AssetsFieldProperties> || field is IField<ReferencesFieldProperties>) && !value.IsNull()) this.value = value;
{
switch (field)
{
case IField<AssetsFieldProperties> assetsField:
return Visit(assetsField, value, oldReferences);
case IField<ReferencesFieldProperties> referencesField:
return Visit(referencesField, value, oldReferences);
}
}
return value; this.oldReferences = oldReferences;
} }
private static JToken Visit(IField<AssetsFieldProperties> field, JToken value, IEnumerable<Guid> oldReferences) public static JToken CleanReferences(IField field, JToken value, ICollection<Guid> oldReferences)
{ {
var oldIds = GetIds(value); return field.Accept(new ReferencesCleaner(value, oldReferences));
var newIds = oldIds.Except(oldReferences).ToList(); }
return oldIds.Count != newIds.Count ? JToken.FromObject(newIds) : value; public JToken Visit(IArrayField field)
{
return value;
} }
private static JToken Visit(IField<ReferencesFieldProperties> field, JToken value, ICollection<Guid> oldReferences) public JToken Visit(IField<AssetsFieldProperties> field)
{
return CleanIds();
}
public JToken Visit(IField<ReferencesFieldProperties> field)
{ {
if (oldReferences.Contains(field.Properties.SchemaId)) if (oldReferences.Contains(field.Properties.SchemaId))
{ {
return new JArray(); return new JArray();
} }
var oldIds = GetIds(value); return CleanIds();
var newIds = oldIds.Except(oldReferences).ToList();
return oldIds.Count != newIds.Count ? JToken.FromObject(newIds) : value;
} }
private static List<Guid> GetIds(JToken value) private JToken CleanIds()
{ {
try var ids = value.ToGuidSet();
{
return value?.ToObject<List<Guid>>() ?? EmptyIds; var isRemoved = false;
}
catch foreach (var oldReference in oldReferences)
{ {
return EmptyIds; isRemoved |= ids.Remove(oldReference);
} }
return isRemoved ? ids.ToJToken() : value;
}
public JToken Visit(IField<BooleanFieldProperties> field)
{
return value;
}
public JToken Visit(IField<DateTimeFieldProperties> field)
{
return value;
}
public JToken Visit(IField<GeolocationFieldProperties> field)
{
return value;
}
public JToken Visit(IField<JsonFieldProperties> field)
{
return value;
}
public JToken Visit(IField<NumberFieldProperties> field)
{
return value;
}
public JToken Visit(IField<StringFieldProperties> field)
{
return value;
}
public JToken Visit(IField<TagsFieldProperties> field)
{
return value;
} }
} }
} }

69
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs

@ -0,0 +1,69 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ReferencesExtensions
{
public static IEnumerable<Guid> ExtractReferences(this IField field, JToken value)
{
return ReferencesExtractor.ExtractReferences(field, value);
}
public static JToken CleanReferences(this IField field, JToken value, ICollection<Guid> oldReferences)
{
if (value.IsNull())
{
return value;
}
return ReferencesCleaner.CleanReferences(field, value, oldReferences);
}
public static JToken ToJToken(this HashSet<Guid> ids)
{
var result = new JArray();
foreach (var id in ids)
{
result.Add(new JValue(id));
}
return result;
}
public static HashSet<Guid> ToGuidSet(this JToken value)
{
if (value is JArray ids)
{
var result = new HashSet<Guid>();
foreach (var id in ids)
{
if (id.Type == JTokenType.Guid)
{
result.Add((Guid)id);
}
else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid))
{
result.Add(guid);
}
}
return result;
}
return new HashSet<Guid>();
}
}
}

101
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -13,50 +13,93 @@ using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ReferencesExtractor public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>>
{ {
public static IEnumerable<Guid> ExtractReferences(this IField field, JToken value) private readonly JToken value;
{
switch (field)
{
case IField<AssetsFieldProperties> assetsField:
return Visit(assetsField, value);
case IField<ReferencesFieldProperties> referencesField: private ReferencesExtractor(JToken value)
return Visit(referencesField, value); {
} this.value = value;
}
return Enumerable.Empty<Guid>(); public static IEnumerable<Guid> ExtractReferences(IField field, JToken value)
{
return field.Accept(new ReferencesExtractor(value));
} }
public static IEnumerable<Guid> Visit(IField<AssetsFieldProperties> field, JToken value) public IEnumerable<Guid> Visit(IArrayField field)
{ {
IEnumerable<Guid> result; var result = new List<Guid>();
try
{ if (value is JArray items)
result = value?.ToObject<List<Guid>>();
}
catch
{ {
result = null; foreach (JObject item in value)
{
foreach (var nestedField in field.Fields)
{
if (item.TryGetValue(field.Name, out var value))
{
result.AddRange(nestedField.Accept(new ReferencesExtractor(value)));
}
}
}
} }
return result ?? Enumerable.Empty<Guid>(); return result;
} }
private static IEnumerable<Guid> Visit(IField<ReferencesFieldProperties> field, JToken value) public IEnumerable<Guid> Visit(IField<AssetsFieldProperties> field)
{ {
IEnumerable<Guid> result; var ids = value.ToGuidSet();
try
{ return ids;
result = value?.ToObject<List<Guid>>() ?? Enumerable.Empty<Guid>(); }
}
catch public IEnumerable<Guid> Visit(IField<ReferencesFieldProperties> field)
{
var ids = value.ToGuidSet();
if (field.Properties.SchemaId != Guid.Empty)
{ {
result = Enumerable.Empty<Guid>(); ids.Add(field.Properties.SchemaId);
} }
return result.Union(new[] { field.Properties.SchemaId }); return ids;
}
public IEnumerable<Guid> Visit(IField<BooleanFieldProperties> field)
{
return Enumerable.Empty<Guid>();
}
public IEnumerable<Guid> Visit(IField<DateTimeFieldProperties> field)
{
return Enumerable.Empty<Guid>();
}
public IEnumerable<Guid> Visit(IField<GeolocationFieldProperties> field)
{
return Enumerable.Empty<Guid>();
}
public IEnumerable<Guid> Visit(IField<JsonFieldProperties> field)
{
return Enumerable.Empty<Guid>();
}
public IEnumerable<Guid> Visit(IField<NumberFieldProperties> field)
{
return Enumerable.Empty<Guid>();
}
public IEnumerable<Guid> Visit(IField<StringFieldProperties> field)
{
return Enumerable.Empty<Guid>();
}
public IEnumerable<Guid> Visit(IField<TagsFieldProperties> field)
{
return Enumerable.Empty<Guid>();
} }
} }
} }

4
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs

@ -95,14 +95,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
EnsurePropertiesInitialized(); EnsurePropertiesInitialized();
fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this)).Value = value; fieldProperties.GetOrAdd(propertyName, this, (k, c) => new ContentDataProperty(c)).Value = value;
} }
public override PropertyDescriptor GetOwnProperty(string propertyName) public override PropertyDescriptor GetOwnProperty(string propertyName)
{ {
EnsurePropertiesInitialized(); EnsurePropertiesInitialized();
return fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this, new ContentFieldObject(this, new ContentFieldData(), false))); return fieldProperties.GetOrAdd(propertyName, this, (k, c) => new ContentDataProperty(c, new ContentFieldObject(c, new ContentFieldData(), false)));
} }
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties() public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -160,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return null; return null;
} }
return schema != null ? contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType()) : null; return schema != null ? contentDataTypes.GetOrAddNew(schema) : null;
} }
public IGraphType GetContentType(Guid schemaId) public IGraphType GetContentType(Guid schemaId)

2
src/Squidex.Infrastructure.Redis/RedisPubSub.cs

@ -56,7 +56,7 @@ namespace Squidex.Infrastructure
{ {
var typeName = typeof(T).FullName; var typeName = typeof(T).FullName;
return (RedisSubscription<T>)subscriptions.GetOrAdd(typeName, c => new RedisSubscription<T>(redisSubscriber.Value, c, log)); return (RedisSubscription<T>)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription<T>(c.redisSubscriber.Value, k, c.log));
} }
} }
} }

12
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -172,6 +172,18 @@ namespace Squidex.Infrastructure
return result; return result;
} }
public static TValue GetOrAdd<TKey, TContext, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TContext context, Func<TKey, TContext, TValue> creator)
{
if (!dictionary.TryGetValue(key, out var result))
{
result = creator(key, context);
dictionary.Add(key, result);
}
return result;
}
public static void Foreach<T>(this IEnumerable<T> collection, Action<T> action) public static void Foreach<T>(this IEnumerable<T> collection, Action<T> action)
{ {
foreach (var item in collection) foreach (var item in collection)

2
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs

@ -242,7 +242,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private SwaggerOperations AddOperation(SwaggerOperationMethod method, string entityName, string path, Action<SwaggerOperation> updater) private SwaggerOperations AddOperation(SwaggerOperationMethod method, string entityName, string path, Action<SwaggerOperation> updater)
{ {
var operations = document.Paths.GetOrAdd(path, k => new SwaggerOperations()); var operations = document.Paths.GetOrAddNew(path);
var operation = new SwaggerOperation(); var operation = new SwaggerOperation();
updater(operation); updater(operation);

2
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs

@ -78,7 +78,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{ {
name = char.ToUpperInvariant(name[0]) + name.Substring(1); name = char.ToUpperInvariant(name[0]) + name.Substring(1);
return new JsonSchema4 { Reference = document.Definitions.GetOrAdd(name, x => schema) }; return new JsonSchema4 { Reference = document.Definitions.GetOrAdd(name, schema, (k, c) => c) };
} }
} }
} }

222
tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs

@ -0,0 +1,222 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Schemas;
using Xunit;
#pragma warning disable SA1310 // Field names must not contain underscore
namespace Squidex.Domain.Apps.Core.Model.Schemas
{
public class ArrayfieldTests
{
private readonly JsonSerializer serializer = TestData.DefaultSerializer();
private readonly ArrayField parent_0 = Fields.Array(100, "root", Partitioning.Invariant);
[Fact]
public void Should_add_field()
{
var field = CreateField(1);
var parent_1 = parent_0.AddField(field);
Assert.Empty(parent_0.Fields);
Assert.Equal(field, parent_1.FieldsById[1]);
}
[Fact]
public void Should_throw_exception_if_adding_field_with_name_that_already_exists()
{
var parent_1 = parent_0.AddField(CreateField(1));
Assert.Throws<ArgumentException>(() => parent_1.AddNumber(2, "my-field-1"));
}
[Fact]
public void Should_throw_exception_if_adding_field_with_id_that_already_exists()
{
var parent_1 = parent_0.AddField(CreateField(1));
Assert.Throws<ArgumentException>(() => parent_1.AddNumber(1, "my-field-2"));
}
[Fact]
public void Should_hide_field()
{
var parent_1 = parent_0.AddField(CreateField(1));
var parent_2 = parent_1.UpdateField(1, f => f.Hide());
var parent_3 = parent_2.UpdateField(1, f => f.Hide());
Assert.False(parent_1.FieldsById[1].IsHidden);
Assert.True(parent_3.FieldsById[1].IsHidden);
}
[Fact]
public void Should_return_same_parent_if_field_to_hide_does_not_exist()
{
var parent_1 = parent_0.UpdateField(1, f => f.Hide()); ;
Assert.Same(parent_0, parent_1);
}
[Fact]
public void Should_show_field()
{
var parent_1 = parent_0.AddField(CreateField(1));
var parent_2 = parent_1.UpdateField(1, f => f.Hide());
var parent_3 = parent_2.UpdateField(1, f => f.Show());
var parent_4 = parent_3.UpdateField(1, f => f.Show());
Assert.True(parent_2.FieldsById[1].IsHidden);
Assert.False(parent_4.FieldsById[1].IsHidden);
}
[Fact]
public void Should_return_same_parent_if_field_to_show_does_not_exist()
{
var parent_1 = parent_0.UpdateField(1, f => f.Show());
Assert.Same(parent_0, parent_1);
}
[Fact]
public void Should_disable_field()
{
var parent_1 = parent_0.AddField(CreateField(1));
var parent_2 = parent_1.UpdateField(1, f => f.Disable());
var parent_3 = parent_2.UpdateField(1, f => f.Disable());
Assert.False(parent_1.FieldsById[1].IsDisabled);
Assert.True(parent_3.FieldsById[1].IsDisabled);
}
[Fact]
public void Should_return_same_parent_if_field_to_disable_does_not_exist()
{
var parent_1 = parent_0.UpdateField(1, f => f.Disable());
Assert.Same(parent_0, parent_1);
}
[Fact]
public void Should_enable_field()
{
var parent_1 = parent_0.AddField(CreateField(1));
var parent_2 = parent_1.UpdateField(1, f => f.Disable());
var parent_3 = parent_2.UpdateField(1, f => f.Enable());
var parent_4 = parent_3.UpdateField(1, f => f.Enable());
Assert.True(parent_2.FieldsById[1].IsDisabled);
Assert.False(parent_4.FieldsById[1].IsDisabled);
}
[Fact]
public void Should_return_same_parent_if_field_to_enable_does_not_exist()
{
var parent_1 = parent_0.UpdateField(1, f => f.Enable());
Assert.Same(parent_0, parent_1);
}
[Fact]
public void Should_update_field()
{
var properties = new NumberFieldProperties();
var parent_1 = parent_0.AddField(CreateField(1));
var parent_2 = parent_1.UpdateField(1, f => f.Update(properties));
Assert.NotSame(properties, parent_1.FieldsById[1].RawProperties);
Assert.Same(properties, parent_2.FieldsById[1].RawProperties);
}
[Fact]
public void Should_throw_exception_if_updating_with_invalid_properties_type()
{
var parent_1 = parent_0.AddField(CreateField(1));
Assert.Throws<ArgumentException>(() => parent_1.UpdateField(1, f => f.Update(new StringFieldProperties())));
}
[Fact]
public void Should_return_same_parent_if_field_to_update_does_not_exist()
{
var parent_1 = parent_0.UpdateField(1, f => f.Update(new StringFieldProperties()));
Assert.Same(parent_0, parent_1);
}
[Fact]
public void Should_delete_field()
{
var parent_1 = parent_0.AddField(CreateField(1));
var parent_2 = parent_1.DeleteField(1);
Assert.Empty(parent_2.FieldsById);
}
[Fact]
public void Should_return_same_parent_if_field_to_delete_does_not_exist()
{
var parent_1 = parent_0.DeleteField(1);
Assert.Same(parent_0, parent_1);
}
[Fact]
public void Should_reorder_fields()
{
var field1 = CreateField(1);
var field2 = CreateField(2);
var field3 = CreateField(3);
var parent_1 = parent_0.AddField(field1);
var parent_2 = parent_1.AddField(field2);
var parent_3 = parent_2.AddField(field3);
var parent_4 = parent_3.ReorderFields(new List<long> { 3, 2, 1 });
Assert.Equal(new List<NestedField> { field3, field2, field1 }, parent_4.Fields.ToList());
}
[Fact]
public void Should_throw_exception_if_not_all_fields_are_covered_for_reordering()
{
var field1 = CreateField(1);
var field2 = CreateField(2);
var parent_1 = parent_0.AddField(field1);
var parent_2 = parent_1.AddField(field2);
Assert.Throws<ArgumentException>(() => parent_2.ReorderFields(new List<long> { 1 }));
}
[Fact]
public void Should_throw_exception_if_field_to_reorder_does_not_exist()
{
var field1 = CreateField(1);
var field2 = CreateField(2);
var parent_1 = parent_0.AddField(field1);
var parent_2 = parent_1.AddField(field2);
Assert.Throws<ArgumentException>(() => parent_2.ReorderFields(new List<long> { 1, 4 }));
}
private static NestedField<NumberFieldProperties> CreateField(int id)
{
return Fields.Number(id, $"my-field-{id}");
}
}
}

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

@ -21,30 +21,35 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE);
private readonly RootField<StringFieldProperties> stringLanguageField = Fields.String(1, "1", Partitioning.Language); private readonly RootField<StringFieldProperties> stringLanguageField = Fields.String(1, "1", Partitioning.Language);
private readonly RootField<StringFieldProperties> stringInvariantField = Fields.String(1, "1", Partitioning.Invariant); private readonly RootField<StringFieldProperties> stringInvariantField = 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); private readonly RootField<NumberFieldProperties> numberField = Fields.Number(1, "1", Partitioning.Invariant);
[Fact] [Fact]
public void Should_encode_json_values() public void Should_encode_json_value()
{ {
var source = var source = JToken.FromObject(new { Value = 1 });
new ContentFieldData()
.AddValue("en", null)
.AddValue("de", JToken.FromObject(new { Value = 1 }));
var result = FieldConverters.EncodeJson()(source, Fields.Json(1, "1", Partitioning.Invariant)); var result = ValueConverters.EncodeJson()(source, jsonField);
Assert.Null(result["en"]); Assert.True(result.Type == JTokenType.String);
Assert.True(result["de"].Type == JTokenType.String);
} }
[Fact] [Fact]
public void Should_return_same_values_if_encoding_non_json_field() public void Should_return_same_value_if_encoding_null_value()
{ {
var source = var source = JValue.CreateNull();
new ContentFieldData()
.AddValue("en", null);
var result = FieldConverters.EncodeJson()(source, stringLanguageField); var result = ValueConverters.EncodeJson()(source, jsonField);
Assert.Same(source, result);
}
[Fact]
public void Should_return_same_value_if_encoding_non_json_field()
{
var source = (JToken)"NO-JSON";
var result = ValueConverters.EncodeJson()(source, stringLanguageField);
Assert.Same(source, result); Assert.Same(source, result);
} }
@ -52,15 +57,31 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
[Fact] [Fact]
public void Should_decode_json_values() public void Should_decode_json_values()
{ {
var source = var source = "e30=";
new ContentFieldData()
.AddValue("en", null) var result = ValueConverters.DecodeJson()(source, jsonField);
.AddValue("de", "e30=");
Assert.True(result is JObject);
}
[Fact]
public void Should_return_same_value_if_decoding_null_value()
{
var source = JValue.CreateNull();
var result = ValueConverters.DecodeJson()(source, jsonField);
Assert.Same(source, result);
}
var result = FieldConverters.DecodeJson()(source, Fields.Json(1, "1", Partitioning.Invariant)); [Fact]
public void Should_return_same_value_if_decoding_non_json_field()
{
var source = JValue.CreateNull();
Assert.Null(result["en"]); var result = ValueConverters.EncodeJson()(source, stringLanguageField);
Assert.True(result["de"] is JObject);
Assert.Same(source, result);
} }
[Fact] [Fact]
@ -77,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
} }
[Fact] [Fact]
public void Should_return_null_values_if_all_value_is_invalid() public void Should_return_null_values_any_value_is_invalid()
{ {
var source = var source =
new ContentFieldData() new ContentFieldData()
@ -89,18 +110,6 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
Assert.Null(result); Assert.Null(result);
} }
[Fact]
public void Should_return_same_values_if_decoding_non_json_field()
{
var source =
new ContentFieldData()
.AddValue("en", null);
var result = FieldConverters.DecodeJson()(source, stringLanguageField);
Assert.Same(source, result);
}
[Fact] [Fact]
public void Should_return_same_values_if_field_not_hidden() public void Should_return_same_values_if_field_not_hidden()
{ {

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

@ -69,7 +69,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
new ContentFieldData() new ContentFieldData()
.AddValue("iv", new JArray(id1.ToString(), id2.ToString()))); .AddValue("iv", new JArray(id1.ToString(), id2.ToString())));
var actual = input.Convert(schema, FieldReferencesConverter.CleanReferences(new[] { id2 })); var converter = FieldConverters.ForValues(FieldReferencesConverter.CleanReferences(new[] { id2 }));
var actual = input.Convert(schema, converter);
var cleanedValue = (JArray)actual[5]["iv"]; var cleanedValue = (JArray)actual[5]["iv"];
@ -91,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
} }
[Fact] [Fact]
public void Should_empty_list_from_assets_field_for_referenced_ids_when_null() public void Should_return_empty_list_from_assets_field_for_referenced_ids_when_null()
{ {
var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant); var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant);
@ -101,7 +103,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
} }
[Fact] [Fact]
public void Should_empty_list_from_assets_field_for_referenced_ids_when_other_type() public void Should_return_empty_list_from_assets_field_for_referenced_ids_when_other_type()
{ {
var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant); var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant);
@ -111,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
} }
[Fact] [Fact]
public void Should_empty_list_from_non_references_field() public void Should_return_empty_list_from_non_references_field()
{ {
var sut = Fields.String(1, "my-string", Partitioning.Invariant); var sut = Fields.String(1, "my-string", Partitioning.Invariant);

Loading…
Cancel
Save