Browse Source

Fixes/components (#733)

* Fixes

* Fix infinite loop.
pull/734/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
a293edb643
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldBase.cs
  2. 30
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs
  3. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs
  4. 23
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IMetadataProvider.cs
  5. 32
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ResolvedComponents.cs
  6. 42
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  7. 12
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/StringFormatter.cs
  8. 16
      backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueFactory.cs
  9. 30
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  10. 16
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  11. 41
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  12. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs
  13. 31
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs
  14. 15
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs
  15. 41
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs
  16. 16
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs
  17. 36
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  18. 15
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs
  19. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  20. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  21. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/AdaptIdVisitor.cs
  22. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  23. 64
      backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs
  24. 84
      backend/src/Squidex.Domain.Apps.Entities/AppProviderExtensions.cs
  25. 16
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs
  26. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs
  27. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/OperationContext.cs
  28. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs
  29. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs
  30. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs
  31. 31
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  32. 57
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs
  33. 18
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs
  34. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs
  35. 28
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  36. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs
  37. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs
  38. 17
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  39. 20
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
  40. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs
  41. 16
      backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterVisitor.cs
  42. 4
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentOpenApiController.cs
  43. 6
      backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/Builder.cs
  44. 11
      backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs
  45. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  46. 12
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
  47. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
  48. 30
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
  49. 3
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs
  50. 7
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs
  51. 43
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs
  52. 59
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs
  53. 31
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs
  54. 8
      backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs
  55. 18
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
  56. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs
  57. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs
  58. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs
  59. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs
  60. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs
  61. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs
  62. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs
  63. 2
      frontend/app/shared/state/contents.forms.ts

32
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldBase.cs

@ -5,25 +5,16 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class FieldBase : IMetadataProvider
public abstract class FieldBase
{
private Dictionary<string, object> metadata;
public long Id { get; }
public string Name { get; }
public IDictionary<string, object> Metadata
{
get => metadata ??= new Dictionary<string, object>();
}
protected FieldBase(long id, string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
@ -33,26 +24,5 @@ namespace Squidex.Domain.Apps.Core.Schemas
Name = name;
}
public T? GetMetadata<T>(string key, T? defaultValue = default)
{
var local = metadata;
return local != null && local.TryGetValue(key, out var item) ? (T)item : defaultValue;
}
public T GetMetadata<T>(string key, Func<T> defaultValueFactory)
{
var local = metadata;
return local != null && local.TryGetValue(key, out var item) ? (T)item : defaultValueFactory();
}
public bool HasMetadata(string key)
{
var local = metadata;
return local?.ContainsKey(key) == true;
}
}
}

30
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
@ -26,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return fields.Where(x => IsForApi(x, withHidden));
}
public static IEnumerable<IRootField> GetSharedFields(this IField field, ImmutableList<DomainId>? schemaIds, bool withHidden)
public static IEnumerable<IRootField> GetSharedFields(this ResolvedComponents components, ImmutableList<DomainId>? schemaIds, bool withHidden)
{
if (schemaIds == null || schemaIds.Count == 0)
{
@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
var allFields =
schemaIds
.Select(x => field.GetResolvedSchema(x)).NotNull()
.Select(x => components.Get(x)).NotNull()
.SelectMany(x => x.Fields.ForApi(withHidden))
.GroupBy(x => new { x.Name, Type = x.RawProperties.GetType() }).Where(x => x.Count() == 1)
.Select(x => x.First());
@ -43,31 +42,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
return allFields;
}
public static T SetResolvedSchema<T>(this T metadataProvider, DomainId id, Schema schema) where T : IMetadataProvider
{
var key = $"ResolvedSchema_{id}";
metadataProvider.Metadata[key] = schema;
return metadataProvider;
}
public static Schema? GetResolvedSchema<T>(this T metadataProvider, object id) where T : IMetadataProvider
{
var key = $"ResolvedSchema_{id}";
return metadataProvider.GetMetadata<Schema>(key);
}
public static bool TryGetResolvedSchema<T>(this T metadataProvider, object id, [MaybeNullWhen(false)] out Schema schema) where T : IMetadataProvider
{
var key = $"ResolvedSchema_{id}";
schema = metadataProvider.GetMetadata<Schema>(key);
return schema != null;
}
public static bool IsForApi<T>(this T field, bool withHidden = false) where T : IField
{
return (withHidden || !field.IsHidden) && !field.RawProperties.IsUIProperty();

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

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Core.Schemas
{
public interface IField : IFieldSettings, IMetadataProvider
public interface IField : IFieldSettings
{
long Id { get; }

23
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IMetadataProvider.cs

@ -1,23 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core.Schemas
{
public interface IMetadataProvider
{
IDictionary<string, object> Metadata { get; }
T? GetMetadata<T>(string key, T? defaultValue = default);
T GetMetadata<T>(string key, Func<T> defaultValueFactory);
bool HasMetadata(string key);
}
}

32
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ResolvedComponents.cs

@ -0,0 +1,32 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class ResolvedComponents : ImmutableDictionary<DomainId, Schema>
{
public static readonly ResolvedComponents Empty = new ResolvedComponents();
private ResolvedComponents()
{
}
public ResolvedComponents(IDictionary<DomainId, Schema> inner)
: base(inner)
{
}
public Schema? Get(DomainId schemaId)
{
return this.GetOrDefault(schemaId);
}
}
}

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

@ -176,7 +176,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
};
}
public static FieldConverter ForValues(params ValueConverter[] converters)
public static FieldConverter ForValues(ResolvedComponents components, params ValueConverter[] converters)
{
return (data, field) =>
{
@ -184,7 +184,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var (key, value) in data)
{
var newValue = ConvertByType(field, value, null, converters);
var newValue = ConvertByType(field, value, null, converters, components);
if (newValue == null)
{
@ -202,25 +202,27 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
};
}
private static IJsonValue? ConvertByType<T>(T field, IJsonValue? value, IArrayField? parent, ValueConverter[] converters) where T : IField
private static IJsonValue? ConvertByType<T>(T field, IJsonValue? value, IArrayField? parent, ValueConverter[] converters,
ResolvedComponents components) where T : IField
{
switch (field)
{
case IArrayField arrayField:
return ConvertArray(arrayField, value, converters);
return ConvertArray(arrayField, value, converters, components);
case IField<ComponentFieldProperties>:
return ConvertComponent(field, value, converters);
return ConvertComponent(value, converters, components);
case IField<ComponentsFieldProperties>:
return ConvertComponents(field, value, converters);
return ConvertComponents(value, converters, components);
default:
return ConvertValue(field, value, parent, converters);
}
}
private static IJsonValue? ConvertArray(IArrayField field, IJsonValue? value, ValueConverter[] converters)
private static IJsonValue? ConvertArray(IArrayField field, IJsonValue? value, ValueConverter[] converters,
ResolvedComponents components)
{
if (value is JsonArray array)
{
@ -228,7 +230,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
for (int i = 0, j = 0; i < array.Count; i++, j++)
{
var newValue = ConvertArrayItem(field, array[i], converters);
var newValue = ConvertArrayItem(field, array[i], converters, components);
if (newValue == null)
{
@ -249,7 +251,8 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return null;
}
private static IJsonValue? ConvertComponents(IField field, IJsonValue? value, ValueConverter[] converters)
private static IJsonValue? ConvertComponents(IJsonValue? value, ValueConverter[] converters,
ResolvedComponents components)
{
if (value is JsonArray array)
{
@ -257,7 +260,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
for (int i = 0, j = 0; i < array.Count; i++, j++)
{
var newValue = ConvertComponent(field, array[i], converters);
var newValue = ConvertComponent(array[i], converters, components);
if (newValue == null)
{
@ -278,13 +281,16 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return null;
}
private static IJsonValue? ConvertComponent(IField field, IJsonValue? value, ValueConverter[] converters)
private static IJsonValue? ConvertComponent(IJsonValue? value, ValueConverter[] converters,
ResolvedComponents components)
{
if (value is JsonObject obj && obj.TryGetValue<JsonString>(Component.Discriminator, out var type))
{
if (field.TryGetResolvedSchema(type.Value, out var schema))
var id = DomainId.Create(type.Value);
if (components.TryGetValue(id, out var schema))
{
return ConvertNested(schema.FieldCollection, obj, null, converters);
return ConvertNested(schema.FieldCollection, obj, null, converters, components);
}
else
{
@ -295,17 +301,19 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return null;
}
private static IJsonValue? ConvertArrayItem(IArrayField field, IJsonValue? value, ValueConverter[] converters)
private static IJsonValue? ConvertArrayItem(IArrayField field, IJsonValue? value, ValueConverter[] converters,
ResolvedComponents components)
{
if (value is JsonObject obj)
{
return ConvertNested(field.FieldCollection, obj, field, converters);
return ConvertNested(field.FieldCollection, obj, field, converters, components);
}
return null;
}
private static IJsonValue ConvertNested<T>(FieldCollection<T> fields, JsonObject source, IArrayField? parent, ValueConverter[] converters) where T : IField
private static IJsonValue ConvertNested<T>(FieldCollection<T> fields, JsonObject source, IArrayField? parent, ValueConverter[] converters,
ResolvedComponents components) where T : IField
{
JsonObject? result = null;
@ -315,7 +323,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
if (fields.ByName.TryGetValue(key, out var field))
{
newValue = ConvertByType(field, value, parent, converters);
newValue = ConvertByType(field, value, parent, converters, components);
}
else if (key != Component.Discriminator)
{

12
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/StringFormatter.cs

@ -9,21 +9,15 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public sealed class StringFormatter : IFieldPropertiesVisitor<string, StringFormatter.Args>
{
private static readonly StringFormatter Instance = new StringFormatter();
public readonly struct Args
{
public readonly IJsonValue Value;
public Args(IJsonValue value)
{
Value = value;
}
}
public sealed record Args(IJsonValue Value);
private StringFormatter()
{

16
backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueFactory.cs

@ -12,25 +12,15 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.DefaultValues
{
public sealed class DefaultValueFactory : IFieldPropertiesVisitor<IJsonValue, DefaultValueFactory.Args>
{
private static readonly DefaultValueFactory Instance = new DefaultValueFactory();
public readonly struct Args
{
public readonly Instant Now;
public readonly string Partition;
public Args(Instant now, string partition)
{
Now = now;
Partition = partition;
}
}
public sealed record Args(Instant Now, string Partition);
private DefaultValueFactory()
{

30
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -16,53 +16,63 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ContentReferencesExtensions
{
public static HashSet<DomainId> GetReferencedIds(this ContentData source, Schema schema, int referencesPerField = int.MaxValue)
public static HashSet<DomainId> GetReferencedIds(this ContentData source, Schema schema,
ResolvedComponents components, int referencesPerField = int.MaxValue)
{
Guard.NotNull(schema, nameof(schema));
var ids = new HashSet<DomainId>();
AddReferencedIds(source, schema, ids, referencesPerField);
AddReferencedIds(source, schema, ids, components, referencesPerField);
return ids;
}
public static void AddReferencedIds(this ContentData source, Schema schema, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
public static void AddReferencedIds(this ContentData source, Schema schema, HashSet<DomainId> result,
ResolvedComponents components, int referencesPerField = int.MaxValue)
{
Guard.NotNull(schema, nameof(schema));
AddReferencedIds(source, schema.Fields, result, referencesPerField);
AddReferencedIds(source, schema.Fields, result, components, referencesPerField);
}
public static void AddReferencedIds(this ContentData source, IEnumerable<IField> fields, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
public static void AddReferencedIds(this ContentData source, IEnumerable<IField> fields, HashSet<DomainId> result,
ResolvedComponents components, int referencesPerField = int.MaxValue)
{
Guard.NotNull(fields, nameof(fields));
Guard.NotNull(result, nameof(result));
Guard.NotNull(components, nameof(components));
foreach (var field in fields)
{
AddReferencedIds(source, result, referencesPerField, field);
AddReferencedIds(field, source, result, components, referencesPerField);
}
}
private static void AddReferencedIds(ContentData source, HashSet<DomainId> result, int referencesPerField, IField field)
private static void AddReferencedIds(IField field, ContentData source, HashSet<DomainId> result,
ResolvedComponents components, int referencesPerField = int.MaxValue)
{
Guard.NotNull(components, nameof(components));
if (source.TryGetValue(field.Name, out var fieldData) && fieldData != null)
{
foreach (var partitionValue in fieldData)
{
ReferencesExtractor.Extract(field, partitionValue.Value, result, referencesPerField);
ReferencesExtractor.Extract(field, partitionValue.Value, result, referencesPerField, components);
}
}
}
public static HashSet<DomainId> GetReferencedIds(this IField field, IJsonValue? value, int referencesPerField = int.MaxValue)
public static HashSet<DomainId> GetReferencedIds(this IField field, IJsonValue? value,
ResolvedComponents components, int referencesPerField = int.MaxValue)
{
Guard.NotNull(components, nameof(components));
var result = new HashSet<DomainId>();
if (value != null)
{
ReferencesExtractor.Extract(field, value, result, referencesPerField);
ReferencesExtractor.Extract(field, value, result, referencesPerField, components);
}
return result;

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

@ -10,25 +10,15 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
internal sealed class ReferencesCleaner : IFieldVisitor<IJsonValue, ReferencesCleaner.Args>
{
private static readonly ReferencesCleaner Instance = new ReferencesCleaner();
public readonly struct Args
{
public readonly IJsonValue Value;
public readonly HashSet<DomainId> ValidIds;
public Args(IJsonValue value, HashSet<DomainId> validIds)
{
Value = value;
ValidIds = validIds;
}
}
public sealed record Args(IJsonValue Value, ISet<DomainId> ValidIds);
private ReferencesCleaner()
{

41
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -11,35 +11,23 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
internal sealed class ReferencesExtractor : IFieldVisitor<None, ReferencesExtractor.Args>
{
private static readonly ReferencesExtractor Instance = new ReferencesExtractor();
public readonly struct Args
{
public readonly IJsonValue Value;
public readonly HashSet<DomainId> Result;
public readonly int ResultLimit;
public Args(IJsonValue value, HashSet<DomainId> result, int take)
{
Value = value;
Result = result;
ResultLimit = take;
}
}
public sealed record Args(IJsonValue Value, ISet<DomainId> Result, int Take, ResolvedComponents Components);
private ReferencesExtractor()
{
}
public static None Extract(IField field, IJsonValue? value, HashSet<DomainId> result, int take)
public static None Extract(IField field, IJsonValue? value, HashSet<DomainId> result, int take, ResolvedComponents components)
{
var args = new Args(value ?? JsonValue.Null, result, take);
var args = new Args(value ?? JsonValue.Null, result, take, components);
return field.Accept(Instance, args);
}
@ -78,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public None Visit(IField<ComponentFieldProperties> field, Args args)
{
ExtractFromComponent(field, args.Value, args);
ExtractFromComponent(args.Value, args);
return None.Value;
}
@ -89,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
for (var i = 0; i < array.Count; i++)
{
ExtractFromComponent(field, array[i], args);
ExtractFromComponent(array[i], args);
}
}
@ -139,21 +127,26 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
if (obj.TryGetValue(nestedField.Name, out var nestedValue))
{
nestedField.Accept(this, new Args(nestedValue, args.Result, args.ResultLimit));
nestedField.Accept(this, args with { Value = nestedValue });
}
}
}
}
private void ExtractFromComponent(IField field, IJsonValue value, Args args)
private void ExtractFromComponent(IJsonValue value, Args args)
{
if (value is JsonObject obj && obj.TryGetValue<JsonString>(Component.Discriminator, out var type) && field.TryGetResolvedSchema(type.Value, out var schema))
if (value is JsonObject obj && obj.TryGetValue<JsonString>(Component.Discriminator, out var type))
{
var id = DomainId.Create(type.Value);
if (args.Components.TryGetValue(id, out var schema))
{
foreach (var componentField in schema.Fields)
{
if (obj.TryGetValue(componentField.Name, out var componentValue))
{
componentField.Accept(this, new Args(componentValue, args.Result, args.ResultLimit));
componentField.Accept(this, args with { Value = componentValue });
}
}
}
}
@ -173,7 +166,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
added++;
if (added >= args.ResultLimit)
if (added >= args.Take)
{
break;
}

5
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs

@ -26,7 +26,8 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
return field.Replace("_", "-");
}
public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, PartitionResolver partitionResolver, EdmTypeFactory typeFactory)
public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, PartitionResolver partitionResolver, EdmTypeFactory typeFactory,
ResolvedComponents components)
{
Guard.NotNull(typeFactory, nameof(typeFactory));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
continue;
}
var fieldEdmType = EdmTypeVisitor.BuildType(field, typeFactory);
var fieldEdmType = EdmTypeVisitor.BuildType(field, typeFactory, components);
if (fieldEdmType == null)
{

31
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs

@ -10,6 +10,8 @@ using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Text;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{
internal sealed class EdmTypeVisitor : IFieldVisitor<IEdmTypeReference?, EdmTypeVisitor.Args>
@ -18,32 +20,15 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
private static readonly EdmComplexType JsonType = new EdmComplexType("Squidex", "Json", null, false, true);
private static readonly EdmTypeVisitor Instance = new EdmTypeVisitor();
public readonly struct Args
{
public readonly EdmTypeFactory Factory;
public readonly int Level;
public Args(EdmTypeFactory factory, int level)
{
Factory = factory;
Level = level;
}
public Args Increment()
{
return new Args(Factory, Level + 1);
}
}
public sealed record Args(EdmTypeFactory Factory, ResolvedComponents Components, int Level = 0);
private EdmTypeVisitor()
{
}
public static IEdmTypeReference? BuildType(IField field, EdmTypeFactory factory)
public static IEdmTypeReference? BuildType(IField field, EdmTypeFactory factory, ResolvedComponents components)
{
var args = new Args(factory, 0);
var args = new Args(factory, components);
return field.Accept(Instance, args);
}
@ -65,12 +50,12 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
public IEdmTypeReference? Visit(IField<ComponentFieldProperties> field, Args args)
{
return CreateNestedType(field, field.GetSharedFields(field.Properties.SchemaIds, true), args);
return CreateNestedType(field, args.Components.GetSharedFields(field.Properties.SchemaIds, true), args);
}
public IEdmTypeReference? Visit(IField<ComponentsFieldProperties> field, Args args)
{
return CreateNestedType(field, field.GetSharedFields(field.Properties.SchemaIds, true), args);
return CreateNestedType(field, args.Components.GetSharedFields(field.Properties.SchemaIds, true), args);
}
public IEdmTypeReference? Visit(IField<DateTimeFieldProperties> field, Args args)
@ -139,7 +124,7 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
if (created)
{
var nestedArgs = args.Increment();
var nestedArgs = args with { Level = args.Level + 1 };
foreach (var sharedField in nested)
{

15
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs

@ -14,7 +14,8 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{
public static class JsonSchemaExtensions
{
public static JsonSchema BuildFlatJsonSchema(this Schema schema, SchemaResolver schemaResolver)
public static JsonSchema BuildFlatJsonSchema(this Schema schema, SchemaResolver schemaResolver,
ResolvedComponents components)
{
Guard.NotNull(schemaResolver, nameof(schemaResolver));
@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
foreach (var field in schema.Fields.ForApi())
{
var property = JsonTypeVisitor.BuildProperty(field);
var property = JsonTypeVisitor.BuildProperty(field, components);
if (property != null)
{
@ -37,7 +38,8 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return jsonSchema;
}
public static JsonSchema BuildDynamicJsonSchema(this Schema schema, SchemaResolver schemaResolver, bool withHidden = false)
public static JsonSchema BuildDynamicJsonSchema(this Schema schema, SchemaResolver schemaResolver,
ResolvedComponents components, bool withHidden = false)
{
Guard.NotNull(schemaResolver, nameof(schemaResolver));
@ -45,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
foreach (var field in schema.Fields.ForApi(withHidden))
{
var propertyItem = JsonTypeVisitor.BuildProperty(field, schemaResolver, withHidden);
var propertyItem = JsonTypeVisitor.BuildProperty(field, components, schemaResolver, withHidden);
if (propertyItem != null)
{
@ -61,7 +63,8 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return jsonSchema;
}
public static JsonSchema BuildJsonSchema(this Schema schema, PartitionResolver partitionResolver, bool withHidden = false)
public static JsonSchema BuildJsonSchema(this Schema schema, PartitionResolver partitionResolver,
ResolvedComponents components, bool withHidden = false)
{
Guard.NotNull(partitionResolver, nameof(partitionResolver));
@ -75,7 +78,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
foreach (var partitionKey in partitioning.AllKeys)
{
var propertyItem = JsonTypeVisitor.BuildProperty(field, withHiddenFields: withHidden);
var propertyItem = JsonTypeVisitor.BuildProperty(field, components, withHiddenFields: withHidden);
if (propertyItem != null)
{

41
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs

@ -15,6 +15,8 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{
public delegate JsonSchema SchemaResolver(string name, Func<JsonSchema> schema);
@ -24,36 +26,15 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
private const int MaxDepth = 5;
private static readonly JsonTypeVisitor Instance = new JsonTypeVisitor();
public readonly struct Args
{
public readonly SchemaResolver? SchemaResolver;
public readonly bool WithHiddenFields;
public readonly int Level;
public Args(SchemaResolver? schemaResolver, bool withHiddenFields, int level)
{
SchemaResolver = schemaResolver;
WithHiddenFields = withHiddenFields;
Level = level;
}
public Args Increment()
{
return new Args(SchemaResolver, WithHiddenFields, Level + 1);
}
}
public sealed record Args(ResolvedComponents Components, SchemaResolver? SchemaResolver, bool WithHiddenFields, int Level = 0);
private JsonTypeVisitor()
{
}
public static JsonSchemaProperty? BuildProperty(IField field, SchemaResolver? schemaResolver = null, bool withHiddenFields = false)
public static JsonSchemaProperty? BuildProperty(IField field, ResolvedComponents components, SchemaResolver? schemaResolver = null, bool withHiddenFields = false)
{
var args = new Args(schemaResolver, withHiddenFields, 0);
var args = new Args(components, schemaResolver, withHiddenFields, 0);
return field.Accept(Instance, args);
}
@ -67,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var itemSchema = SchemaBuilder.Object();
var nestedArgs = args.Increment();
var nestedArgs = args with { Level = args.Level + 1 };
foreach (var nestedField in field.Fields.ForApi(args.WithHiddenFields))
{
@ -104,7 +85,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var property = SchemaBuilder.ObjectProperty();
BuildComponent(property, field, field.Properties.SchemaIds, args);
BuildComponent(property, field.Properties.SchemaIds, args);
return property;
}
@ -118,7 +99,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var itemSchema = SchemaBuilder.Object();
BuildComponent(itemSchema, field, field.Properties.SchemaIds, args);
BuildComponent(itemSchema, field.Properties.SchemaIds, args);
return SchemaBuilder.ArrayProperty(itemSchema);
}
@ -196,13 +177,13 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return null;
}
private void BuildComponent(JsonSchema jsonSchema, IField field, ImmutableList<DomainId>? schemaIds, Args args)
private void BuildComponent(JsonSchema jsonSchema, ImmutableList<DomainId>? schemaIds, Args args)
{
jsonSchema.Properties.Add(Component.Discriminator, SchemaBuilder.StringProperty(isRequired: true));
if (args.SchemaResolver != null)
{
var schemas = schemaIds?.Select(x => field.GetResolvedSchema(x)).NotNull() ?? Enumerable.Empty<Schema>();
var schemas = schemaIds?.Select(x => args.Components.Get(x)).NotNull() ?? Enumerable.Empty<Schema>();
var discriminator = new OpenApiDiscriminator
{
@ -215,7 +196,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var componentSchema = args.SchemaResolver(schemaName, () =>
{
var nestedArgs = args.Increment();
var nestedArgs = args with { Level = args.Level + 1 };
var componentSchema = SchemaBuilder.Object();

16
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs

@ -13,25 +13,15 @@ using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Text;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ValidateContent
{
internal sealed class DefaultFieldValueValidatorsFactory : IFieldVisitor<IEnumerable<IValidator>, DefaultFieldValueValidatorsFactory.Args>
{
private static readonly DefaultFieldValueValidatorsFactory Instance = new DefaultFieldValueValidatorsFactory();
public readonly struct Args
{
public readonly ValidatorContext Context;
public readonly ValidatorFactory Factory;
public Args(ValidatorContext context, ValidatorFactory factory)
{
Context = context;
Factory = factory;
}
}
public sealed record Args(ValidatorContext Context, ValidatorFactory Factory);
private DefaultFieldValueValidatorsFactory()
{

36
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -14,35 +14,27 @@ using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Translations;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonError? Error), JsonValueConverter.Args>
{
private static readonly JsonValueConverter Instance = new JsonValueConverter();
public readonly struct Args
{
public readonly IJsonValue Value;
public readonly IJsonSerializer JsonSerializer;
public Args(IJsonValue value, IJsonSerializer jsonSerializer)
{
Value = value;
JsonSerializer = jsonSerializer;
}
}
public sealed record Args(IJsonValue Value, IJsonSerializer JsonSerializer, ResolvedComponents Components);
private JsonValueConverter()
{
}
public static (object? Result, JsonError? Error) ConvertValue(IField field, IJsonValue value, IJsonSerializer jsonSerializer)
public static (object? Result, JsonError? Error) ConvertValue(IField field, IJsonValue value, IJsonSerializer jsonSerializer,
ResolvedComponents components)
{
Guard.NotNull(field, nameof(field));
Guard.NotNull(value, nameof(value));
var args = new Args(value, jsonSerializer);
var args = new Args(value, jsonSerializer, components);
return field.Accept(Instance, args);
}
@ -59,12 +51,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public (object? Result, JsonError? Error) Visit(IField<ComponentFieldProperties> field, Args args)
{
return ConvertToComponent(field, args.Value);
return ConvertToComponent(args.Value, args.Components);
}
public (object? Result, JsonError? Error) Visit(IField<ComponentsFieldProperties> field, Args args)
{
return ConvertToComponentList(field, args.Value);
return ConvertToComponentList(args.Value, args.Components);
}
public (object? Result, JsonError? Error) Visit(IField<ReferencesFieldProperties> field, Args args)
@ -199,7 +191,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
}
private static (object? Result, JsonError? Error) ConvertToComponentList(IField<ComponentsFieldProperties> field, IJsonValue value)
private static (object? Result, JsonError? Error) ConvertToComponentList(IJsonValue value,
ResolvedComponents components)
{
if (value is JsonArray array)
{
@ -207,7 +200,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
for (var i = 0; i < array.Count; i++)
{
var (item, error) = ConvertToComponent(field, array[i]);
var (item, error) = ConvertToComponent(array[i], components);
if (error != null)
{
@ -250,7 +243,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfObjects")));
}
private static (object? Result, JsonError? Error) ConvertToComponent(IField field, IJsonValue value)
private static (object? Result, JsonError? Error) ConvertToComponent(IJsonValue value,
ResolvedComponents components)
{
if (value is not JsonObject obj)
{
@ -262,7 +256,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidComponentNoType")));
}
if (!field.TryGetResolvedSchema(type, out var schema))
var id = DomainId.Create(type.Value);
if (!components.TryGetValue(id, out var schema))
{
return (null, new JsonError(T.Get("contents.invalidComponentUnknownSchema")));
}

15
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs

@ -12,24 +12,15 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class JsonValueValidator : IFieldVisitor<bool, JsonValueValidator.Args>
{
private static readonly JsonValueValidator Instance = new JsonValueValidator();
public readonly struct Args
{
public readonly IJsonValue Value;
public readonly IJsonSerializer JsonSerializer;
public Args(IJsonValue value, IJsonSerializer jsonSerializer)
{
Value = value;
JsonSerializer = jsonSerializer;
}
}
public sealed record Args(IJsonValue Value, IJsonSerializer JsonSerializer);
private JsonValueValidator()
{

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public IJsonSerializer JsonSerializer { get; }
public ResolvedComponents Components { get; }
public DomainId ContentId { get; }
public bool IsOptional { get; private set; }
@ -28,11 +30,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
NamedId<DomainId> appId,
NamedId<DomainId> schemaId,
Schema schema,
ResolvedComponents components,
DomainId contentId)
: base(appId, schemaId, schema)
{
JsonSerializer = jsonSerializer;
Components = components;
ContentId = contentId;
}

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -1,4 +1,4 @@
// ==========================================================================
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
}
else
{
var (json, error) = JsonValueConverter.ConvertValue(field, jsonValue, context.JsonSerializer);
var (json, error) = JsonValueConverter.ConvertValue(field, jsonValue, context.JsonSerializer, context.Components);
if (error != null)
{

12
backend/src/Squidex.Domain.Apps.Entities.MongoDb/AdaptIdVisitor.cs

@ -12,21 +12,15 @@ using NodaTime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.MongoDb
{
internal sealed class AdaptIdVisitor : TransformVisitor<ClrValue, AdaptIdVisitor.Args>
{
private static readonly AdaptIdVisitor Instance = new AdaptIdVisitor();
public readonly struct Args
{
public readonly DomainId AppId;
public Args(DomainId appId)
{
AppId = appId;
}
}
public sealed record Args(DomainId AppId);
private AdaptIdVisitor()
{

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -162,7 +162,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
if (schema != null)
{
entity.ReferencedIds = entity.Data.GetReferencedIds(schema.SchemaDef);
var components = await appProvider.GetComponentsAsync(schema);
entity.ReferencedIds = entity.Data.GetReferencedIds(schema.SchemaDef, components);
}
else
{

64
backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -9,7 +9,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Caching;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Rules;
@ -17,7 +16,6 @@ using Squidex.Domain.Apps.Entities.Rules.Indexes;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Entities
@ -109,8 +107,6 @@ namespace Squidex.Domain.Apps.Entities
if (schema != null)
{
await ResolveSchemaAsync(appId, schema.SchemaDef);
localCache.Add(cacheKey, schema);
localCache.Add(SchemaCacheKey(appId, schema.Id), schema);
}
@ -131,8 +127,6 @@ namespace Squidex.Domain.Apps.Entities
if (schema != null)
{
await ResolveSchemaAsync(appId, schema.SchemaDef);
localCache.Add(cacheKey, schema);
localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema);
}
@ -163,11 +157,6 @@ namespace Squidex.Domain.Apps.Entities
localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema);
}
foreach (var schema in schemas)
{
await ResolveSchemaAsync(appId, schema.SchemaDef);
}
return schemas;
}
@ -188,59 +177,6 @@ namespace Squidex.Domain.Apps.Entities
return rules.Find(x => x.Id == id);
}
private async Task ResolveSchemaAsync(DomainId appId, Schema schema)
{
async Task ResolveWithIdsAsync(IField field, ImmutableList<DomainId>? schemaIds)
{
if (schemaIds != null)
{
foreach (var schemaId in schemaIds)
{
if (!field.TryGetResolvedSchema(schemaId, out _))
{
var resolvedEntity = await GetSchemaAsync(appId, schemaId, true);
if (resolvedEntity != null)
{
field.SetResolvedSchema(schemaId, resolvedEntity.SchemaDef);
}
}
}
}
}
async Task ResolveArrayAsync(IArrayField arrayField)
{
foreach (var nestedField in arrayField.Fields)
{
await ResolveAsync(nestedField);
}
}
async Task ResolveAsync(IField field)
{
switch (field)
{
case IField<ComponentFieldProperties> component:
await ResolveWithIdsAsync(field, component.Properties.SchemaIds);
break;
case IField<ComponentsFieldProperties> components:
await ResolveWithIdsAsync(field, components.Properties.SchemaIds);
break;
case IArrayField arrayField:
await ResolveArrayAsync(arrayField);
break;
}
}
foreach (var field in schema.Fields)
{
await ResolveAsync(field);
}
}
private static string AppCacheKey(DomainId appId)
{
return $"APPS_ID_{appId}";

84
backend/src/Squidex.Domain.Apps.Entities/AppProviderExtensions.cs

@ -0,0 +1,84 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities
{
public static class AppProviderExtensions
{
public static async Task<ResolvedComponents> GetComponentsAsync(this IAppProvider appProvider, ISchemaEntity schema)
{
Dictionary<DomainId, Schema>? result = null;
var appId = schema.AppId.Id;
async Task ResolveWithIdsAsync(IField field, ImmutableList<DomainId>? schemaIds)
{
if (schemaIds != null)
{
foreach (var schemaId in schemaIds)
{
if (result == null || !result.TryGetValue(schemaId, out _))
{
var resolvedEntity = await appProvider.GetSchemaAsync(appId, schemaId, true);
if (resolvedEntity != null)
{
result ??= new Dictionary<DomainId, Schema>();
result[schemaId] = resolvedEntity.SchemaDef;
}
}
}
}
}
async Task ResolveArrayAsync(IArrayField arrayField)
{
foreach (var nestedField in arrayField.Fields)
{
await ResolveAsync(nestedField);
}
}
async Task ResolveAsync(IField field)
{
switch (field)
{
case IField<ComponentFieldProperties> component:
await ResolveWithIdsAsync(field, component.Properties.SchemaIds);
break;
case IField<ComponentsFieldProperties> components:
await ResolveWithIdsAsync(field, components.Properties.SchemaIds);
break;
case IArrayField arrayField:
await ResolveArrayAsync(arrayField);
break;
}
}
foreach (var field in schema.SchemaDef.Fields)
{
await ResolveAsync(field);
}
if (result == null)
{
return ResolvedComponents.Empty;
}
return new ResolvedComponents(result);
}
}
}

16
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs

@ -11,25 +11,15 @@ using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Assets.Queries
{
internal sealed class FilterTagTransformer : AsyncTransformVisitor<ClrValue, FilterTagTransformer.Args>
{
private static readonly FilterTagTransformer Instance = new FilterTagTransformer();
public readonly struct Args
{
public readonly DomainId AppId;
public readonly ITagService TagService;
public Args(DomainId appId, ITagService tagService)
{
AppId = appId;
TagService = tagService;
}
}
public sealed record Args(DomainId AppId, ITagService TagService);
private FilterTagTransformer()
{

1
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs

@ -109,6 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
context.App.NamedId(),
context.Schema.NamedId(),
context.SchemaDef,
context.Components,
context.ContentId)
.Optimized(optimize).AsPublishing(published);

5
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/OperationContext.cs

@ -34,6 +34,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
public DomainId ContentId { get; init; }
public ResolvedComponents Components { get; init; }
public Func<IContentEntity> ContentProvider { get; init; }
public IContentEntity Content
@ -69,10 +71,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
throw new DomainObjectNotFoundException(command.SchemaId.Id.ToString());
}
var components = await appProvider.GetComponentsAsync(schema);
return new OperationContext(services)
{
App = app,
Actor = command.Actor,
Components = components,
ContentProvider = snapshot,
ContentId = command.ContentId,
Schema = schema,

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs

@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
foreach (var schemaInfo in schemaInfos)
{
var contentType = new ContentGraphType(this, schemaInfo);
var contentType = new ContentGraphType(schemaInfo);
contentTypes[schemaInfo] = contentType;
contentResultTypes[schemaInfo] = new ContentResultGraphType(contentType, schemaInfo);
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
foreach (var schemaInfo in allSchemas)
{
var componentType = new ComponentGraphType(this, schemaInfo);
var componentType = new ComponentGraphType(schemaInfo);
componentTypes[schemaInfo] = componentType;
}

6
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs

@ -15,17 +15,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentGraphType : ObjectGraphType<JsonObject>
{
public ComponentGraphType(Builder builder, SchemaInfo schemaInfo)
public ComponentGraphType(SchemaInfo schemaInfo)
{
Name = schemaInfo.ComponentType;
Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
IsTypeOf = CheckType(schemaInfo.Schema.Id.ToString());
}
public void Initialize(Builder builder, SchemaInfo schemaInfo)
{
Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
AddField(ContentFields.SchemaId);
foreach (var fieldInfo in schemaInfo.Fields)

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs

@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
private readonly DomainId schemaId;
public ContentGraphType(Builder builder, SchemaInfo schemaInfo)
public ContentGraphType(SchemaInfo schemaInfo)
{
schemaId = schemaInfo.Schema.Id;
@ -31,7 +31,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)
{
Name = schemaInfo.ContentType;
AddField(ContentFields.Id);
@ -83,6 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
AddReferencingQueries(builder, other);
}
AddResolvedInterface(builder.SharedTypes.ContentInterface);
Description = $"The structure of a {schemaInfo.DisplayName} content type.";

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

@ -5,11 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
@ -20,18 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public sealed class ContentEnricher : IContentEnricher
{
private readonly IEnumerable<IContentEnricherStep> steps;
private readonly Lazy<IContentQueryService> contentQuery;
private readonly IAppProvider appProvider;
private IContentQueryService ContentQuery
{
get => contentQuery.Value;
}
public ContentEnricher(IEnumerable<IContentEnricherStep> steps, Lazy<IContentQueryService> contentQuery)
public ContentEnricher(IEnumerable<IContentEnricherStep> steps, IAppProvider appProvider)
{
this.steps = steps;
this.contentQuery = contentQuery;
this.appProvider = appProvider;
}
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, bool cloneData, Context context,
@ -84,11 +79,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
if (context.App != null)
{
var schemaCache = new Dictionary<DomainId, Task<ISchemaEntity>>();
var schemaCache = new Dictionary<DomainId, Task<(ISchemaEntity, ResolvedComponents)>>();
Task<ISchemaEntity> GetSchema(DomainId id)
Task<(ISchemaEntity, ResolvedComponents)> GetSchema(DomainId id)
{
return schemaCache.GetOrAdd(id, x => ContentQuery.GetSchemaOrThrowAsync(context, x.ToString()));
return schemaCache.GetOrAdd(id, async x =>
{
var schema = await appProvider.GetSchemaAsync(context.App.Id, x, false);
if (schema == null)
{
throw new DomainObjectNotFoundException(x.ToString());
}
var components = await appProvider.GetComponentsAsync(schema);
return (schema, components);
});
}
foreach (var step in steps)

57
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs

@ -40,12 +40,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
private readonly JsonSchema genericJsonSchema = ContentJsonSchemaBuilder.BuildSchema("Content", null, false, true);
private readonly IMemoryCache cache;
private readonly IJsonSerializer jsonSerializer;
private readonly IAppProvider appprovider;
private readonly ITextIndex textIndex;
private readonly ContentOptions options;
public ContentQueryParser(IMemoryCache cache, IJsonSerializer jsonSerializer, ITextIndex textIndex, IOptions<ContentOptions> options)
public ContentQueryParser(IAppProvider appprovider, ITextIndex textIndex, IOptions<ContentOptions> options,
IMemoryCache cache, IJsonSerializer jsonSerializer)
{
this.jsonSerializer = jsonSerializer;
this.appprovider = appprovider;
this.textIndex = textIndex;
this.cache = cache;
this.options = options.Value;
@ -58,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
using (Profiler.TraceMethod<ContentQueryParser>())
{
var query = ParseClrQuery(context, q, schema);
var query = await ParseClrQueryAsync(context, q, schema);
await TransformFilterAsync(query, context, schema);
@ -113,21 +116,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
}
private ClrQuery ParseClrQuery(Context context, Q q, ISchemaEntity? schema)
private async Task<ClrQuery> ParseClrQueryAsync(Context context, Q q, ISchemaEntity? schema)
{
var components = ResolvedComponents.Empty;
if (schema != null)
{
components = await appprovider.GetComponentsAsync(schema);
}
var query = q.Query;
if (!string.IsNullOrWhiteSpace(q.JsonQueryString))
{
query = ParseJson(context, schema, q.JsonQueryString);
query = ParseJson(context, schema, q.JsonQueryString, components);
}
else if (q?.JsonQuery != null)
{
query = ParseJson(context, schema, q.JsonQuery);
query = ParseJson(context, schema, q.JsonQuery, components);
}
else if (!string.IsNullOrWhiteSpace(q?.ODataQuery))
{
query = ParseOData(context, schema, q.ODataQuery);
query = ParseOData(context, schema, q.ODataQuery, components);
}
return query;
@ -158,25 +168,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
}
private ClrQuery ParseJson(Context context, ISchemaEntity? schema, Query<IJsonValue> query)
private ClrQuery ParseJson(Context context, ISchemaEntity? schema, Query<IJsonValue> query,
ResolvedComponents components)
{
var jsonSchema = BuildJsonSchema(context, schema);
var jsonSchema = BuildJsonSchema(context, schema, components);
return jsonSchema.Convert(query);
}
private ClrQuery ParseJson(Context context, ISchemaEntity? schema, string json)
private ClrQuery ParseJson(Context context, ISchemaEntity? schema, string json,
ResolvedComponents components)
{
var jsonSchema = BuildJsonSchema(context, schema);
var jsonSchema = BuildJsonSchema(context, schema, components);
return jsonSchema.Parse(json, jsonSerializer);
}
private ClrQuery ParseOData(Context context, ISchemaEntity? schema, string odata)
private ClrQuery ParseOData(Context context, ISchemaEntity? schema, string odata,
ResolvedComponents components)
{
try
{
var model = BuildEdmModel(context, schema);
var model = BuildEdmModel(context, schema, components);
return model.ParseQuery(odata).ToQuery();
}
@ -196,7 +209,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
}
private JsonSchema BuildJsonSchema(Context context, ISchemaEntity? schema)
private JsonSchema BuildJsonSchema(Context context, ISchemaEntity? schema,
ResolvedComponents components)
{
if (schema == null)
{
@ -209,13 +223,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
entry.AbsoluteExpirationRelativeToNow = CacheTime;
return BuildJsonSchema(schema.SchemaDef, context.App, context.IsFrontendClient);
return BuildJsonSchema(schema.SchemaDef, context.App, components, context.IsFrontendClient);
});
return result;
}
private IEdmModel BuildEdmModel(Context context, ISchemaEntity? schema)
private IEdmModel BuildEdmModel(Context context, ISchemaEntity? schema,
ResolvedComponents components)
{
if (schema == null)
{
@ -228,20 +243,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
entry.AbsoluteExpirationRelativeToNow = CacheTime;
return BuildEdmModel(schema.SchemaDef, context.App, context.IsFrontendClient);
return BuildEdmModel(schema.SchemaDef, context.App, components, context.IsFrontendClient);
});
return result;
}
private static JsonSchema BuildJsonSchema(Schema schema, IAppEntity app, bool withHiddenFields)
private static JsonSchema BuildJsonSchema(Schema schema, IAppEntity app,
ResolvedComponents components, bool withHiddenFields)
{
var dataSchema = schema.BuildJsonSchema(app.PartitionResolver(), withHiddenFields);
var dataSchema = schema.BuildJsonSchema(app.PartitionResolver(), components, withHiddenFields);
return ContentJsonSchemaBuilder.BuildSchema(schema.DisplayName(), dataSchema, false, true);
}
private static EdmModel BuildEdmModel(Schema schema, IAppEntity app, bool withHiddenFields)
private static EdmModel BuildEdmModel(Schema schema, IAppEntity app,
ResolvedComponents components, bool withHiddenFields)
{
var model = new EdmModel();
@ -272,7 +289,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
return (result, true);
});
var schemaType = schema.BuildEdmType(withHiddenFields, app.PartitionResolver(), typeFactory);
var schemaType = schema.BuildEdmType(withHiddenFields, app.PartitionResolver(), typeFactory, components);
return BuildEdmModel(app.Name.ToPascalCase(), schema.Name, model, schemaType);
}

18
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs

@ -11,27 +11,15 @@ using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
internal sealed class GeoQueryTransformer : AsyncTransformVisitor<ClrValue, GeoQueryTransformer.Args>
{
public static readonly GeoQueryTransformer Instance = new GeoQueryTransformer();
public readonly struct Args
{
public readonly ITextIndex TextIndex;
public readonly ISchemaEntity Schema;
public readonly Context Context;
public Args(Context context, ISchemaEntity schema, ITextIndex textIndex)
{
Context = context;
Schema = schema;
TextIndex = textIndex;
}
}
public sealed record Args(Context Context, ISchemaEntity Schema, ITextIndex TextIndex);
private GeoQueryTransformer()
{

3
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs

@ -8,12 +8,13 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public delegate Task<ISchemaEntity> ProvideSchema(DomainId id);
public delegate Task<(ISchemaEntity Schema, ResolvedComponents Components)> ProvideSchema(DomainId id);
public interface IContentEnricherStep
{

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

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
@ -24,25 +25,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
public sealed class ConvertData : IContentEnricherStep
{
private readonly IUrlGenerator urlGenerator;
private readonly IJsonSerializer jsonSerializer;
private readonly IAssetRepository assetRepository;
private readonly IContentRepository contentRepository;
private readonly FieldConverter excludedChangedField;
private readonly FieldConverter excludedChangedValue;
private readonly FieldConverter excludedHiddenField;
private readonly FieldConverter excludedHiddenValue;
public ConvertData(IUrlGenerator urlGenerator, IJsonSerializer jsonSerializer,
IAssetRepository assetRepository, IContentRepository contentRepository)
{
this.urlGenerator = urlGenerator;
this.jsonSerializer = jsonSerializer;
this.assetRepository = assetRepository;
this.contentRepository = contentRepository;
excludedChangedField = FieldConverters.ExcludeChangedTypes(jsonSerializer);
excludedChangedValue = FieldConverters.ForValues(ValueConverters.ExcludeChangedTypes(jsonSerializer));
excludedHiddenField = FieldConverters.ExcludeHidden;
excludedHiddenValue = FieldConverters.ForValues(ValueConverters.ExcludeHidden);
}
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas,
@ -50,13 +48,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
var referenceCleaner = await CleanReferencesAsync(context, contents, schemas, ct);
var converters = GenerateConverters(context, referenceCleaner).ToArray();
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
ct.ThrowIfCancellationRequested();
var schema = await schemas(group.Key);
var (schema, components) = await schemas(group.Key);
var converters = GenerateConverters(context, components, referenceCleaner).ToArray();
foreach (var content in group)
{
@ -74,11 +72,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var (schema, components) = await schemas(group.Key);
foreach (var content in group)
{
content.Data.AddReferencedIds(schema.SchemaDef, ids);
content.Data.AddReferencedIds(schema.SchemaDef, ids, components);
}
}
@ -113,20 +111,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
return result;
}
private IEnumerable<FieldConverter> GenerateConverters(Context context, ValueConverter? cleanReferences)
private IEnumerable<FieldConverter> GenerateConverters(Context context, ResolvedComponents components, ValueConverter? cleanReferences)
{
if (!context.IsFrontendClient)
{
yield return excludedHiddenField;
yield return excludedHiddenValue;
yield return FieldConverters.ForValues(components, ValueConverters.ExcludeHidden);
}
yield return excludedChangedField;
yield return excludedChangedValue;
yield return FieldConverters.ForValues(components, ValueConverters.ExcludeChangedTypes(jsonSerializer));
if (cleanReferences != null)
{
yield return FieldConverters.ForValues(cleanReferences);
yield return FieldConverters.ForValues(components, cleanReferences);
}
yield return FieldConverters.ResolveInvariant(context.App.Languages);
@ -154,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
var resolveAssetUrls = ValueConverters.ResolveAssetUrls(appId, assetUrls, urlGenerator);
yield return FieldConverters.ForValues(resolveAssetUrls);
yield return FieldConverters.ForValues(components, resolveAssetUrls);
}
}
}

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

@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
ct.ThrowIfCancellationRequested();
var schema = await schemas(group.Key);
var (schema, _) = await schemas(group.Key);
foreach (var content in group)
{

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

@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
ct.ThrowIfCancellationRequested();
var schema = await schemas(group.Key);
var (schema, _) = await schemas(group.Key);
var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged();

17
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs

@ -46,23 +46,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var (schema, components) = await schemas(group.Key);
AddAssetIds(ids, schema, group);
AddAssetIds(ids, schema, components, group);
}
var assets = await GetAssetsAsync(context, ids, ct);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var (schema, components) = await schemas(group.Key);
ResolveAssetsUrls(schema, group, assets);
ResolveAssetsUrls(schema, components, group, assets);
}
}
}
private void ResolveAssetsUrls(ISchemaEntity schema, IGrouping<DomainId, ContentEntity> contents, ILookup<DomainId, IEnrichedAssetEntity> assets)
private void ResolveAssetsUrls(ISchemaEntity schema, ResolvedComponents components,
IGrouping<DomainId, ContentEntity> contents, ILookup<DomainId, IEnrichedAssetEntity> assets)
{
foreach (var field in schema.SchemaDef.ResolvingAssets())
{
@ -77,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
foreach (var (partitionKey, partitionValue) in fieldData)
{
var referencedAsset =
field.GetReferencedIds(partitionValue)
field.GetReferencedIds(partitionValue, components)
.Select(x => assets[x])
.SelectMany(x => x)
.FirstOrDefault();
@ -133,11 +134,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
return assets.ToLookup(x => x.Id);
}
private static void AddAssetIds(HashSet<DomainId> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents)
private static void AddAssetIds(HashSet<DomainId> ids, ISchemaEntity schema, ResolvedComponents components, IEnumerable<ContentEntity> contents)
{
foreach (var content in contents)
{
content.Data.AddReferencedIds(schema.SchemaDef.ResolvingAssets(), ids, 1);
content.Data.AddReferencedIds(schema.SchemaDef.ResolvingAssets(), ids, components, 1);
}
}

20
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs

@ -48,24 +48,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var (schema, components) = await schemas(group.Key);
AddReferenceIds(ids, schema, group);
AddReferenceIds(ids, schema, components, group);
}
var references = await GetReferencesAsync(context, ids, ct);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var (schema, components) = await schemas(group.Key);
await ResolveReferencesAsync(context, schema, group, references, schemas);
await ResolveReferencesAsync(context, schema, components, group, references, schemas);
}
}
}
private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, IEnumerable<ContentEntity> contents, ILookup<DomainId, IEnrichedContentEntity> references,
ProvideSchema schemas)
private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, ResolvedComponents components,
IEnumerable<ContentEntity> contents, ILookup<DomainId, IEnrichedContentEntity> references, ProvideSchema schemas)
{
var formatted = new Dictionary<IContentEntity, JsonObject>();
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
foreach (var (partition, partitionValue) in fieldData)
{
var referencedContents =
field.GetReferencedIds(partitionValue)
field.GetReferencedIds(partitionValue, components)
.Select(x => references[x])
.SelectMany(x => x)
.ToList();
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
var reference = referencedContents[0];
var referencedSchema = await schemas(reference.SchemaId.Id);
var (referencedSchema, _) = await schemas(reference.SchemaId.Id);
requestCache.AddDependency(referencedSchema.UniqueId, referencedSchema.Version);
requestCache.AddDependency(reference.UniqueId, reference.Version);
@ -138,11 +138,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
return value;
}
private static void AddReferenceIds(HashSet<DomainId> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents)
private static void AddReferenceIds(HashSet<DomainId> ids, ISchemaEntity schema, ResolvedComponents components, IEnumerable<ContentEntity> contents)
{
foreach (var content in contents)
{
content.Data.AddReferencedIds(schema.SchemaDef.ResolvingReferences(), ids);
content.Data.AddReferencedIds(schema.SchemaDef.ResolvingReferences(), ids, components);
}
}

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

@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var (schema, _) = await schemas(group.Key);
var script = schema.SchemaDef.Scripts.Query;

16
backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterVisitor.cs

@ -11,25 +11,15 @@ using NJsonSchema;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Infrastructure.Queries.Json
{
public sealed class JsonFilterVisitor : FilterNodeVisitor<FilterNode<ClrValue>, IJsonValue, JsonFilterVisitor.Args>
{
private static readonly JsonFilterVisitor Instance = new JsonFilterVisitor();
public struct Args
{
public readonly List<string> Errors;
public JsonSchema Schema;
public Args(JsonSchema schema, List<string> errors)
{
Schema = schema;
Errors = errors;
}
}
public sealed record Args(JsonSchema Schema, List<string> Errors);
private JsonFilterVisitor()
{

4
backend/src/Squidex/Areas/Api/Controllers/Contents/ContentOpenApiController.cs

@ -64,7 +64,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{
var schemas = await appProvider.GetSchemasAsync(AppId);
var openApiDocument = schemasOpenApiGenerator.Generate(HttpContext, App, schemas);
var openApiDocument = await schemasOpenApiGenerator.GenerateAsync(HttpContext, App, schemas);
return Content(openApiDocument.ToJson(), "application/json");
}
@ -77,7 +77,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{
var schemas = await appProvider.GetSchemasAsync(AppId);
var openApiDocument = schemasOpenApiGenerator.Generate(HttpContext, App, schemas, true);
var openApiDocument = await schemasOpenApiGenerator.GenerateAsync(HttpContext, App, schemas, true);
return Content(openApiDocument.ToJson(), "application/json");
}

6
backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/Builder.cs

@ -79,7 +79,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return builder;
}
public OperationsBuilder Schema(Schema schema, bool flat)
public OperationsBuilder Schema(Schema schema, ResolvedComponents components, bool flat)
{
var typeName = schema.TypeName();
@ -87,7 +87,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
var dataSchema = ResolveSchema($"{typeName}DataDto", () =>
{
return schema.BuildDynamicJsonSchema(ResolveSchema);
return schema.BuildDynamicJsonSchema(ResolveSchema, components);
});
var contentSchema = ResolveSchema($"{typeName}ContentDto", () =>
@ -98,7 +98,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{
contentDataSchema = ResolveSchema($"{typeName}FlatDataDto", () =>
{
return schema.BuildFlatJsonSchema(ResolveSchema);
return schema.BuildFlatJsonSchema(ResolveSchema, components);
});
}

11
backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs

@ -8,11 +8,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using NJsonSchema;
using NSwag;
using NSwag.Generation;
using NSwag.Generation.Processors.Contexts;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Hosting;
@ -25,24 +27,27 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{
public sealed class SchemasOpenApiGenerator
{
private readonly IAppProvider appProvider;
private readonly IUrlGenerator urlGenerator;
private readonly OpenApiDocumentGeneratorSettings schemaSettings;
private readonly OpenApiSchemaGenerator schemaGenerator;
private readonly IRequestCache requestCache;
public SchemasOpenApiGenerator(
IAppProvider appProvider,
IUrlGenerator urlGenerator,
OpenApiDocumentGeneratorSettings schemaSettings,
OpenApiSchemaGenerator schemaGenerator,
IRequestCache requestCache)
{
this.appProvider = appProvider;
this.urlGenerator = urlGenerator;
this.schemaSettings = schemaSettings;
this.schemaGenerator = schemaGenerator;
this.requestCache = requestCache;
}
public OpenApiDocument Generate(HttpContext httpContext, IAppEntity app, IEnumerable<ISchemaEntity> schemas, bool flat = false)
public async Task<OpenApiDocument> GenerateAsync(HttpContext httpContext, IAppEntity app, IEnumerable<ISchemaEntity> schemas, bool flat = false)
{
var document = CreateApiDocument(httpContext, app);
@ -68,7 +73,9 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
foreach (var schema in validSchemas)
{
GenerateSchemaOperations(builder.Schema(schema.SchemaDef, flat));
var components = await appProvider.GetComponentsAsync(schema);
GenerateSchemaOperations(builder.Schema(schema.SchemaDef, components, flat));
}
GenerateSharedOperations(builder.Shared());

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs

@ -458,7 +458,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas
public void Should_serialize_and_deserialize_schema()
{
var schemaSource =
TestUtils.MixedSchema(SchemaType.Singleton, false)
TestUtils.MixedSchema(SchemaType.Singleton)
.ChangeCategory("Category")
.SetFieldRules(FieldRule.Hide("2"))
.SetFieldsInLists("field2")

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
@ -17,6 +18,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
public class ContentConversionTests
{
private readonly Schema schema;
private readonly ResolvedComponents components;
public ContentConversionTests()
{
@ -30,8 +32,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddArray(6, "array", Partitioning.Invariant, a => a
.AddAssets(31, "nested"));
schema.FieldsById[1].SetResolvedSchema(DomainId.Empty, schema);
schema.FieldsById[2].SetResolvedSchema(DomainId.Empty, schema);
components = new ResolvedComponents(new Dictionary<DomainId, Schema>
{
[DomainId.Empty] = schema
});
}
[Fact]
@ -113,9 +117,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
JsonValue.Object()))
.Add(Component.Discriminator, DomainId.Empty))));
var converter = new ValueConverter((data, field, parent) => field.Name != "assets1" ? null : data);
var actual =
source.Convert(schema,
FieldConverters.ForValues((data, field, parent) => field.Name != "assets1" ? null : data));
FieldConverters.ForValues(components, converter));
Assert.Equal(expected, actual);
}

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

@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
new ContentFieldData()
.AddInvariant(JsonValue.Object());
var result = FieldConverters.ForValues((value, field, parent) => null)(source, field);
var result = FieldConverters.ForValues(ResolvedComponents.Empty, (value, field, parent) => null)(source, field);
var expected = new ContentFieldData();

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

@ -20,6 +20,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
public class ReferenceExtractionTests
{
private readonly Schema schema;
private readonly ResolvedComponents components;
public ReferenceExtractionTests()
{
@ -35,11 +36,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
.AddComponent(32, "nestedComponent")
.AddComponents(33, "nestedComponents"));
schema.FieldsById[1].SetResolvedSchema(DomainId.Empty, schema);
schema.FieldsById[2].SetResolvedSchema(DomainId.Empty, schema);
((IArrayField)schema.FieldsById[6]).FieldsById[32].SetResolvedSchema(DomainId.Empty, schema);
((IArrayField)schema.FieldsById[6]).FieldsById[33].SetResolvedSchema(DomainId.Empty, schema);
components = new ResolvedComponents(new Dictionary<DomainId, Schema>
{
[DomainId.Empty] = schema
});
}
[Fact]
@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var ids = new HashSet<DomainId>();
input.AddReferencedIds(schema, ids);
input.AddReferencedIds(schema, ids, components);
Assert.Equal(new[] { id1, id2 }, ids);
}
@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var ids = new HashSet<DomainId>();
input.AddReferencedIds(schema, ids, 1);
input.AddReferencedIds(schema, ids, components, 1);
Assert.Equal(new[] { id1 }, ids);
}
@ -193,7 +193,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
.Add(Component.Discriminator, DomainId.Empty))));
var converter =
FieldConverters.ForValues(
FieldConverters.ForValues(components,
ValueReferencesConverter.CleanReferences(new HashSet<DomainId> { id2 }));
var actual = source.Convert(schema, converter);
@ -206,7 +206,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
{
var sut = Fields.String(1, "my-string", Partitioning.Invariant);
var result = sut.GetReferencedIds(JsonValue.Create("invalid")).ToArray();
var result = sut.GetReferencedIds(JsonValue.Create("invalid"), components).ToArray();
Assert.Empty(result);
}
@ -225,7 +225,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
JsonValue.Object()
.Add(field.Name, CreateValue(id1, id2)));
var result = arrayField.GetReferencedIds(value).ToArray();
var result = arrayField.GetReferencedIds(value, components).ToArray();
Assert.Equal(new[] { id1, id2 }, result);
}
@ -234,7 +234,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_item_is_invalid(IField field)
{
var result = field.GetReferencedIds(JsonValue.Array(1)).ToArray();
var result = field.GetReferencedIds(JsonValue.Array(1), components).ToArray();
Assert.Empty(result);
}
@ -243,7 +243,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_is_empty(IField field)
{
var result = field.GetReferencedIds(JsonValue.Array()).ToArray();
var result = field.GetReferencedIds(JsonValue.Array(), components).ToArray();
Assert.Empty(result);
}
@ -252,7 +252,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_is_json_null(IField field)
{
var result = field.GetReferencedIds(null).ToArray();
var result = field.GetReferencedIds(null, components).ToArray();
Assert.Empty(result);
}
@ -261,7 +261,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_is_null(IField field)
{
var result = field.GetReferencedIds(null).ToArray();
var result = field.GetReferencedIds(null, components).ToArray();
Assert.Empty(result);
}
@ -275,7 +275,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var value = CreateValue(id1, id2);
var result = field.GetReferencedIds(value);
var result = field.GetReferencedIds(value, components);
Assert.Equal(new HashSet<DomainId> { id1, id2 }, result);
}

3
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs

@ -8,6 +8,7 @@
using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateEdmSchema
var edmModel =
TestUtils.MixedSchema()
.BuildEdmType(true, languagesConfig.ToResolver(), typeFactory);
.BuildEdmType(true, languagesConfig.ToResolver(), typeFactory, ResolvedComponents.Empty);
Assert.NotNull(edmModel);
}

7
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs

@ -18,7 +18,6 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
{
public class JsonSchemaTests
{
private const int MaxDepth = 5;
private readonly Schema schema = TestUtils.MixedSchema();
[Fact]
@ -26,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
{
var languagesConfig = LanguagesConfig.English.Set(Language.DE);
var jsonSchema = schema.BuildJsonSchema(languagesConfig.ToResolver());
var jsonSchema = schema.BuildJsonSchema(languagesConfig.ToResolver(), ResolvedComponents.Empty);
CheckFields(jsonSchema);
}
@ -36,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
{
var schemaResolver = new SchemaResolver((name, action) => action());
var jsonSchema = schema.BuildDynamicJsonSchema(schemaResolver);
var jsonSchema = schema.BuildDynamicJsonSchema(schemaResolver, ResolvedComponents.Empty);
CheckFields(jsonSchema);
}
@ -51,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
return action();
});
var jsonSchema = schema.BuildFlatJsonSchema(schemaResolver);
var jsonSchema = schema.BuildFlatJsonSchema(schemaResolver, ResolvedComponents.Empty);
CheckFields(jsonSchema);
}

43
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public void Should_instantiate_field()
{
var (_, sut) = Field(new ComponentFieldProperties());
var (_, sut, _) = Field(new ComponentFieldProperties());
Assert.Equal("my-component", sut.Name);
}
@ -32,9 +32,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_component_is_null_and_valid()
{
var (_, sut) = Field(new ComponentFieldProperties());
var (_, sut, components) = Field(new ComponentFieldProperties());
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors, components: components);
Assert.Empty(errors);
}
@ -42,9 +42,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_component_is_valid()
{
var (id, sut) = Field(new ComponentFieldProperties());
var (id, sut, components) = Field(new ComponentFieldProperties());
await sut.ValidateAsync(CreateValue(id.ToString(), "component-field", 1), errors);
await sut.ValidateAsync(CreateValue(id.ToString(), "component-field", 1), errors, components: components);
Assert.Empty(errors);
}
@ -52,9 +52,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_component_is_required()
{
var (_, sut) = Field(new ComponentFieldProperties { IsRequired = true });
var (_, sut, components) = Field(new ComponentFieldProperties { IsRequired = true });
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
@ -63,9 +63,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_component_value_is_required()
{
var (id, sut) = Field(new ComponentFieldProperties { IsRequired = true }, true);
var (id, sut, components) = Field(new ComponentFieldProperties { IsRequired = true }, true);
await sut.ValidateAsync(CreateValue(id.ToString(), "component-field", null), errors);
await sut.ValidateAsync(CreateValue(id.ToString(), "component-field", null), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "component-field: Field is required." });
@ -74,9 +74,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_is_not_valid()
{
var (_, sut) = Field(new ComponentFieldProperties());
var (_, sut, components) = Field(new ComponentFieldProperties());
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors);
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid json object, expected object with 'schemaId' field." });
@ -85,9 +85,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_has_no_discriminator()
{
var (_, sut) = Field(new ComponentFieldProperties());
var (_, sut, components) = Field(new ComponentFieldProperties());
await sut.ValidateAsync(CreateValue(null, "field", 1), errors);
await sut.ValidateAsync(CreateValue(null, "field", 1), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid component. No 'schemaId' field found." });
@ -96,9 +96,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_has_invalid_discriminator()
{
var (_, sut) = Field(new ComponentFieldProperties());
var (_, sut, components) = Field(new ComponentFieldProperties());
await sut.ValidateAsync(CreateValue("invalid", "field", 1), errors);
await sut.ValidateAsync(CreateValue("invalid", "field", 1), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." });
@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
return obj;
}
private static (DomainId, RootField<ComponentFieldProperties>) Field(ComponentFieldProperties properties, bool isRequired = false)
private static (DomainId, RootField<ComponentFieldProperties>, ResolvedComponents) Field(ComponentFieldProperties properties, bool isRequired = false)
{
var schema =
new Schema("my-component")
@ -127,11 +127,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
var id = DomainId.NewGuid();
var field =
Fields.Component(1, "my-component", Partitioning.Invariant, properties)
.SetResolvedSchema(id, schema);
var field = Fields.Component(1, "my-component", Partitioning.Invariant, properties);
return (id, field);
var components = new ResolvedComponents(new Dictionary<DomainId, Schema>
{
[id] = schema
});
return (id, field, components);
}
}
}

59
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public void Should_instantiate_field()
{
var (_, sut) = Field(new ComponentsFieldProperties());
var (_, sut, _) = Field(new ComponentsFieldProperties());
Assert.Equal("my-components", sut.Name);
}
@ -32,9 +32,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_components_are_null_and_valid()
{
var (_, sut) = Field(new ComponentsFieldProperties());
var (_, sut, components) = Field(new ComponentsFieldProperties());
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors, components: components);
Assert.Empty(errors);
}
@ -42,9 +42,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_components_is_valid()
{
var (id, sut) = Field(new ComponentsFieldProperties());
var (id, sut, components) = Field(new ComponentsFieldProperties());
await sut.ValidateAsync(CreateValue(1, id.ToString(), "component-field", 1), errors);
await sut.ValidateAsync(CreateValue(1, id.ToString(), "component-field", 1), errors, components: components);
Assert.Empty(errors);
}
@ -52,9 +52,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_not_add_error_if_number_of_components_is_equal_to_min_and_max_components()
{
var (id, sut) = Field(new ComponentsFieldProperties { MinItems = 2, MaxItems = 2 });
var (id, sut, components) = Field(new ComponentsFieldProperties { MinItems = 2, MaxItems = 2 });
await sut.ValidateAsync(CreateValue(2, id.ToString(), "component-field", 1), errors);
await sut.ValidateAsync(CreateValue(2, id.ToString(), "component-field", 1), errors, components: components);
Assert.Empty(errors);
}
@ -62,9 +62,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_components_are_required()
{
var (_, sut) = Field(new ComponentsFieldProperties { IsRequired = true });
var (_, sut, components) = Field(new ComponentsFieldProperties { IsRequired = true });
await sut.ValidateAsync(null, errors);
await sut.ValidateAsync(null, errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
@ -73,9 +73,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_components_value_is_required()
{
var (id, sut) = Field(new ComponentsFieldProperties { IsRequired = true }, true);
var (id, sut, components) = Field(new ComponentsFieldProperties { IsRequired = true }, true);
await sut.ValidateAsync(CreateValue(1, id.ToString(), "component-field", null), errors);
await sut.ValidateAsync(CreateValue(1, id.ToString(), "component-field", null), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "[1].component-field: Field is required." });
@ -84,9 +84,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_is_not_valid()
{
var (_, sut) = Field(new ComponentsFieldProperties());
var (_, sut, components) = Field(new ComponentsFieldProperties());
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors);
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of objects." });
@ -95,9 +95,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_component_is_not_valid()
{
var (_, sut) = Field(new ComponentsFieldProperties());
var (_, sut, components) = Field(new ComponentsFieldProperties());
await sut.ValidateAsync(JsonValue.Array(JsonValue.Create("Invalid")), errors);
await sut.ValidateAsync(JsonValue.Array(JsonValue.Create("Invalid")), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid json object, expected object with 'schemaId' field." });
@ -106,9 +106,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_component_has_no_discriminator()
{
var (_, sut) = Field(new ComponentsFieldProperties());
var (_, sut, components) = Field(new ComponentsFieldProperties());
await sut.ValidateAsync(CreateValue(1, null, "field", 1), errors);
await sut.ValidateAsync(CreateValue(1, null, "field", 1), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid component. No 'schemaId' field found." });
@ -117,9 +117,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_has_invalid_discriminator()
{
var (_, sut) = Field(new ComponentsFieldProperties());
var (_, sut, components) = Field(new ComponentsFieldProperties());
await sut.ValidateAsync(CreateValue(1, "invalid", "field", 1), errors);
await sut.ValidateAsync(CreateValue(1, "invalid", "field", 1), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." });
@ -128,9 +128,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_has_not_enough_components()
{
var (id, sut) = Field(new ComponentsFieldProperties { MinItems = 3 });
var (id, sut, components) = Field(new ComponentsFieldProperties { MinItems = 3 });
await sut.ValidateAsync(CreateValue(2, id.ToString(), "component-field", 1), errors);
await sut.ValidateAsync(CreateValue(2, id.ToString(), "component-field", 1), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." });
@ -139,9 +139,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact]
public async Task Should_add_error_if_value_has_too_much_components()
{
var (id, sut) = Field(new ComponentsFieldProperties { MaxItems = 1 });
var (id, sut, components) = Field(new ComponentsFieldProperties { MaxItems = 1 });
await sut.ValidateAsync(CreateValue(2, id.ToString(), "component-field", 1), errors);
await sut.ValidateAsync(CreateValue(2, id.ToString(), "component-field", 1), errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." });
@ -168,7 +168,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
return result;
}
private static (DomainId, RootField<ComponentsFieldProperties>) Field(ComponentsFieldProperties properties, bool isRequired = false)
private static (DomainId, RootField<ComponentsFieldProperties>, ResolvedComponents) Field(ComponentsFieldProperties properties, bool isRequired = false)
{
var schema =
new Schema("my-component")
@ -177,11 +177,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
var id = DomainId.NewGuid();
var field =
Fields.Components(1, "my-components", Partitioning.Invariant, properties)
.SetResolvedSchema(id, schema);
var field = Fields.Components(1, "my-components", Partitioning.Invariant, properties);
return (id, field);
var components = new ResolvedComponents(new Dictionary<DomainId, Schema>
{
[id] = schema
});
return (id, field, components);
}
}
}

31
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs

@ -31,9 +31,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
Schema? schema = null,
ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null,
ValidationAction action = ValidationAction.Upsert)
ValidationAction action = ValidationAction.Upsert,
ResolvedComponents? components = null,
DomainId? contentId = null)
{
var context = CreateContext(schema, mode, updater, action);
var context = CreateContext(schema, mode, updater, action, components, contentId);
return validator.ValidateAsync(value, context, CreateFormatter(errors));
}
@ -43,9 +45,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null,
IValidatorsFactory? factory = null,
ValidationAction action = ValidationAction.Upsert)
ValidationAction action = ValidationAction.Upsert,
ResolvedComponents? components = null,
DomainId? contentId = null)
{
var context = CreateContext(schema, mode, updater, action);
var context = CreateContext(schema, mode, updater, action, components, contentId);
var validator = new ValidatorBuilder(factory, context).ValueValidator(field);
@ -57,9 +61,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null,
IValidatorsFactory? factory = null,
ValidationAction action = ValidationAction.Upsert)
ValidationAction action = ValidationAction.Upsert,
ResolvedComponents? components = null,
DomainId? contentId = null)
{
var context = CreateContext(schema, mode, updater, action);
var context = CreateContext(schema, mode, updater, action, components, contentId);
var validator = new ValidatorBuilder(factory, context).ContentValidator(partitionResolver);
@ -76,9 +82,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null,
IValidatorsFactory? factory = null,
ValidationAction action = ValidationAction.Upsert)
ValidationAction action = ValidationAction.Upsert,
ResolvedComponents? components = null,
DomainId? contentId = null)
{
var context = CreateContext(schema, mode, updater, action);
var context = CreateContext(schema, mode, updater, action, components, contentId);
var validator = new ValidatorBuilder(factory, context).ContentValidator(partitionResolver);
@ -109,14 +117,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
Schema? schema = null,
ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null,
ValidationAction action = ValidationAction.Upsert)
ValidationAction action = ValidationAction.Upsert,
ResolvedComponents? components = null,
DomainId? contentId = null)
{
var context = new ValidationContext(
TestUtils.DefaultSerializer,
AppId,
SchemaId,
schema ?? new Schema(SchemaId.Name),
DomainId.NewGuid());
components ?? ResolvedComponents.Empty,
contentId ?? DomainId.NewGuid());
context = context.WithMode(mode).WithAction(action);

8
backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs

@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Core.TestHelpers
return new NewtonsoftJsonSerializer(serializerSettings);
}
public static Schema MixedSchema(SchemaType type = SchemaType.Default, bool withMetadata = true)
public static Schema MixedSchema(SchemaType type = SchemaType.Default)
{
var componentId = DomainId.NewGuid();
@ -135,12 +135,6 @@ namespace Squidex.Domain.Apps.Core.TestHelpers
.DisableField(212, 101)
.LockField(105);
if (withMetadata)
{
schema.FieldsById[114].SetResolvedSchema(componentId, schema);
schema.FieldsById[115].SetResolvedSchema(componentId, schema);
}
return schema;
}

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

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class ContentEnricherTests
{
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly ISchemaEntity schema;
private readonly Context requestContext;
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
Schema = await schemas(group.Key);
Schema = (await schemas(group.Key)).Schema;
}
}
}
@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
schema = Mocks.Schema(appId, schemaId);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, schemaId.Id.ToString()))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(schema);
}
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var step1 = A.Fake<IContentEnricherStep>();
var step2 = A.Fake<IContentEnricherStep>();
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
var sut = new ContentEnricher(new[] { step1, step2 }, appProvider);
await sut.EnrichAsync(source, requestContext, default);
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var step1 = A.Fake<IContentEnricherStep>();
var step2 = A.Fake<IContentEnricherStep>();
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
var sut = new ContentEnricher(new[] { step1, step2 }, appProvider);
await sut.EnrichAsync(source, false, requestContext, default);
@ -109,14 +109,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var step1 = new ResolveSchema();
var step2 = new ResolveSchema();
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
var sut = new ContentEnricher(new[] { step1, step2 }, appProvider);
await sut.EnrichAsync(source, false, requestContext, default);
Assert.Same(schema, step1.Schema);
Assert.Same(schema, step1.Schema);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, schemaId.Id.ToString()))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.MustHaveHappenedOnceExactly();
}
@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var source = CreateContent(new ContentData());
var sut = new ContentEnricher(Enumerable.Empty<IContentEnricherStep>(), new Lazy<IContentQueryService>(() => contentQuery));
var sut = new ContentEnricher(Enumerable.Empty<IContentEnricherStep>(), appProvider);
var result = await sut.EnrichAsync(source, true, requestContext, default);
@ -137,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var source = CreateContent(new ContentData());
var sut = new ContentEnricher(Enumerable.Empty<IContentEnricherStep>(), new Lazy<IContentQueryService>(() => contentQuery));
var sut = new ContentEnricher(Enumerable.Empty<IContentEnricherStep>(), appProvider);
var result = await sut.EnrichAsync(source, false, requestContext, default);

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs

@ -26,6 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class ContentQueryParserTests
{
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly ITextIndex textIndex = A.Fake<ITextIndex>();
private readonly ISchemaEntity schema;
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
sut = new ContentQueryParser(cache, TestUtils.DefaultSerializer, textIndex, options);
sut = new ContentQueryParser(appProvider, textIndex, options, cache, TestUtils.DefaultSerializer);
}
[Fact]

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
.AddAssets(31, "nested"));
schema = Mocks.Schema(appId, schemaId, schemaDef);
schemaProvider = x => Task.FromResult(schema);
schemaProvider = x => Task.FromResult((schema, ResolvedComponents.Empty));
sut = new ConvertData(urlGenerator, TestUtils.DefaultSerializer, assetRepository, contentRepository);
}

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
@ -34,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema);
schemaProvider = x => Task.FromResult((schema, ResolvedComponents.Empty));
sut = new EnrichForCaching(requestCache);
}

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs

@ -7,6 +7,7 @@
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
@ -26,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public EnrichWithSchemaTests()
{
schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema);
schemaProvider = x => Task.FromResult((schema, ResolvedComponents.Empty));
sut = new EnrichWithSchema();
}

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs

@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
if (x == schemaId.Id)
{
return Task.FromResult(Mocks.Schema(appId, schemaId, schemaDef));
return Task.FromResult((Mocks.Schema(appId, schemaId, schemaDef), ResolvedComponents.Empty));
}
else
{

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

@ -69,15 +69,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
if (x == schemaId.Id)
{
return Task.FromResult(Mocks.Schema(appId, schemaId, schemaDef));
return Task.FromResult((Mocks.Schema(appId, schemaId, schemaDef), ResolvedComponents.Empty));
}
else if (x == refSchemaId1.Id)
{
return Task.FromResult(Mocks.Schema(appId, refSchemaId1, refSchemaDef));
return Task.FromResult((Mocks.Schema(appId, refSchemaId1, refSchemaDef), ResolvedComponents.Empty));
}
else if (x == refSchemaId2.Id)
{
return Task.FromResult(Mocks.Schema(appId, refSchemaId2, refSchemaDef));
return Task.FromResult((Mocks.Schema(appId, refSchemaId2, refSchemaDef), ResolvedComponents.Empty));
}
else
{

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs

@ -42,11 +42,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
if (x == schemaId.Id)
{
return Task.FromResult(Mocks.Schema(appId, schemaId, schemaDef));
return Task.FromResult((Mocks.Schema(appId, schemaId, schemaDef), ResolvedComponents.Empty));
}
else if (x == schemaWithScriptId.Id)
{
return Task.FromResult(Mocks.Schema(appId, schemaWithScriptId, schemaDefWithScript));
return Task.FromResult((Mocks.Schema(appId, schemaWithScriptId, schemaDefWithScript), ResolvedComponents.Empty));
}
else
{

2
frontend/app/shared/state/contents.forms.ts

@ -601,8 +601,6 @@ export class ComponentForm extends ObjectForm {
if (schemaId) {
this.selectSchema(schemaId);
} else if (this.properties.schemaIds?.length === 1) {
this.selectSchema(this.properties.schemaIds[0]);
}
}

Loading…
Cancel
Save