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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas 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 long Id { get; }
public string Name { get; } public string Name { get; }
public IDictionary<string, object> Metadata
{
get => metadata ??= new Dictionary<string, object>();
}
protected FieldBase(long id, string name) protected FieldBase(long id, string name)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
@ -33,26 +24,5 @@ namespace Squidex.Domain.Apps.Core.Schemas
Name = name; 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
@ -26,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return fields.Where(x => IsForApi(x, withHidden)); 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) if (schemaIds == null || schemaIds.Count == 0)
{ {
@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
var allFields = var allFields =
schemaIds schemaIds
.Select(x => field.GetResolvedSchema(x)).NotNull() .Select(x => components.Get(x)).NotNull()
.SelectMany(x => x.Fields.ForApi(withHidden)) .SelectMany(x => x.Fields.ForApi(withHidden))
.GroupBy(x => new { x.Name, Type = x.RawProperties.GetType() }).Where(x => x.Count() == 1) .GroupBy(x => new { x.Name, Type = x.RawProperties.GetType() }).Where(x => x.Count() == 1)
.Select(x => x.First()); .Select(x => x.First());
@ -43,31 +42,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
return allFields; 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 public static bool IsForApi<T>(this T field, bool withHidden = false) where T : IField
{ {
return (withHidden || !field.IsHidden) && !field.RawProperties.IsUIProperty(); 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 namespace Squidex.Domain.Apps.Core.Schemas
{ {
public interface IField : IFieldSettings, IMetadataProvider public interface IField : IFieldSettings
{ {
long Id { get; } 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) => return (data, field) =>
{ {
@ -184,7 +184,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var (key, value) in data) foreach (var (key, value) in data)
{ {
var newValue = ConvertByType(field, value, null, converters); var newValue = ConvertByType(field, value, null, converters, components);
if (newValue == null) 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) switch (field)
{ {
case IArrayField arrayField: case IArrayField arrayField:
return ConvertArray(arrayField, value, converters); return ConvertArray(arrayField, value, converters, components);
case IField<ComponentFieldProperties>: case IField<ComponentFieldProperties>:
return ConvertComponent(field, value, converters); return ConvertComponent(value, converters, components);
case IField<ComponentsFieldProperties>: case IField<ComponentsFieldProperties>:
return ConvertComponents(field, value, converters); return ConvertComponents(value, converters, components);
default: default:
return ConvertValue(field, value, parent, converters); 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) 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++) 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) if (newValue == null)
{ {
@ -249,7 +251,8 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return null; 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) 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++) 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) if (newValue == null)
{ {
@ -278,13 +281,16 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return null; 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 (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 else
{ {
@ -295,17 +301,19 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return null; 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) if (value is JsonObject obj)
{ {
return ConvertNested(field.FieldCollection, obj, field, converters); return ConvertNested(field.FieldCollection, obj, field, converters, components);
} }
return null; 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; JsonObject? result = null;
@ -315,7 +323,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
if (fields.ByName.TryGetValue(key, out var field)) 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) 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;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
public sealed class StringFormatter : IFieldPropertiesVisitor<string, StringFormatter.Args> public sealed class StringFormatter : IFieldPropertiesVisitor<string, StringFormatter.Args>
{ {
private static readonly StringFormatter Instance = new StringFormatter(); private static readonly StringFormatter Instance = new StringFormatter();
public readonly struct Args public sealed record Args(IJsonValue Value);
{
public readonly IJsonValue Value;
public Args(IJsonValue value)
{
Value = value;
}
}
private StringFormatter() 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;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.DefaultValues namespace Squidex.Domain.Apps.Core.DefaultValues
{ {
public sealed class DefaultValueFactory : IFieldPropertiesVisitor<IJsonValue, DefaultValueFactory.Args> public sealed class DefaultValueFactory : IFieldPropertiesVisitor<IJsonValue, DefaultValueFactory.Args>
{ {
private static readonly DefaultValueFactory Instance = new DefaultValueFactory(); private static readonly DefaultValueFactory Instance = new DefaultValueFactory();
public readonly struct Args public sealed record Args(Instant Now, string Partition);
{
public readonly Instant Now;
public readonly string Partition;
public Args(Instant now, string partition)
{
Now = now;
Partition = partition;
}
}
private DefaultValueFactory() 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 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)); Guard.NotNull(schema, nameof(schema));
var ids = new HashSet<DomainId>(); var ids = new HashSet<DomainId>();
AddReferencedIds(source, schema, ids, referencesPerField); AddReferencedIds(source, schema, ids, components, referencesPerField);
return ids; 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)); 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(fields, nameof(fields));
Guard.NotNull(result, nameof(result)); Guard.NotNull(result, nameof(result));
Guard.NotNull(components, nameof(components));
foreach (var field in fields) 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) if (source.TryGetValue(field.Name, out var fieldData) && fieldData != null)
{ {
foreach (var partitionValue in fieldData) 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>(); var result = new HashSet<DomainId>();
if (value != null) if (value != null)
{ {
ReferencesExtractor.Extract(field, value, result, referencesPerField); ReferencesExtractor.Extract(field, value, result, referencesPerField, components);
} }
return result; 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;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
internal sealed class ReferencesCleaner : IFieldVisitor<IJsonValue, ReferencesCleaner.Args> internal sealed class ReferencesCleaner : IFieldVisitor<IJsonValue, ReferencesCleaner.Args>
{ {
private static readonly ReferencesCleaner Instance = new ReferencesCleaner(); private static readonly ReferencesCleaner Instance = new ReferencesCleaner();
public readonly struct Args public sealed record Args(IJsonValue Value, ISet<DomainId> ValidIds);
{
public readonly IJsonValue Value;
public readonly HashSet<DomainId> ValidIds;
public Args(IJsonValue value, HashSet<DomainId> validIds)
{
Value = value;
ValidIds = validIds;
}
}
private ReferencesCleaner() 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;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
internal sealed class ReferencesExtractor : IFieldVisitor<None, ReferencesExtractor.Args> internal sealed class ReferencesExtractor : IFieldVisitor<None, ReferencesExtractor.Args>
{ {
private static readonly ReferencesExtractor Instance = new ReferencesExtractor(); private static readonly ReferencesExtractor Instance = new ReferencesExtractor();
public readonly struct Args public sealed record Args(IJsonValue Value, ISet<DomainId> Result, int Take, ResolvedComponents Components);
{
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;
}
}
private ReferencesExtractor() 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); return field.Accept(Instance, args);
} }
@ -78,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public None Visit(IField<ComponentFieldProperties> field, Args args) public None Visit(IField<ComponentFieldProperties> field, Args args)
{ {
ExtractFromComponent(field, args.Value, args); ExtractFromComponent(args.Value, args);
return None.Value; return None.Value;
} }
@ -89,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
for (var i = 0; i < array.Count; i++) 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)) 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) foreach (var componentField in schema.Fields)
{ {
if (obj.TryGetValue(componentField.Name, out var componentValue)) 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++; added++;
if (added >= args.ResultLimit) if (added >= args.Take)
{ {
break; 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("_", "-"); 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(typeFactory, nameof(typeFactory));
Guard.NotNull(partitionResolver, nameof(partitionResolver)); Guard.NotNull(partitionResolver, nameof(partitionResolver));
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
continue; continue;
} }
var fieldEdmType = EdmTypeVisitor.BuildType(field, typeFactory); var fieldEdmType = EdmTypeVisitor.BuildType(field, typeFactory, components);
if (fieldEdmType == null) 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.Domain.Apps.Core.Schemas;
using Squidex.Text; using Squidex.Text;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.GenerateEdmSchema namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{ {
internal sealed class EdmTypeVisitor : IFieldVisitor<IEdmTypeReference?, EdmTypeVisitor.Args> 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 EdmComplexType JsonType = new EdmComplexType("Squidex", "Json", null, false, true);
private static readonly EdmTypeVisitor Instance = new EdmTypeVisitor(); private static readonly EdmTypeVisitor Instance = new EdmTypeVisitor();
public readonly struct Args public sealed record Args(EdmTypeFactory Factory, ResolvedComponents Components, int Level = 0);
{
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);
}
}
private EdmTypeVisitor() 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); return field.Accept(Instance, args);
} }
@ -65,12 +50,12 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
public IEdmTypeReference? Visit(IField<ComponentFieldProperties> field, Args args) 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) 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) public IEdmTypeReference? Visit(IField<DateTimeFieldProperties> field, Args args)
@ -139,7 +124,7 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
if (created) if (created)
{ {
var nestedArgs = args.Increment(); var nestedArgs = args with { Level = args.Level + 1 };
foreach (var sharedField in nested) 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 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)); Guard.NotNull(schemaResolver, nameof(schemaResolver));
@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
foreach (var field in schema.Fields.ForApi()) foreach (var field in schema.Fields.ForApi())
{ {
var property = JsonTypeVisitor.BuildProperty(field); var property = JsonTypeVisitor.BuildProperty(field, components);
if (property != null) if (property != null)
{ {
@ -37,7 +38,8 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return jsonSchema; 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)); Guard.NotNull(schemaResolver, nameof(schemaResolver));
@ -45,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
foreach (var field in schema.Fields.ForApi(withHidden)) 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) if (propertyItem != null)
{ {
@ -61,7 +63,8 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return jsonSchema; 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)); Guard.NotNull(partitionResolver, nameof(partitionResolver));
@ -75,7 +78,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
foreach (var partitionKey in partitioning.AllKeys) foreach (var partitionKey in partitioning.AllKeys)
{ {
var propertyItem = JsonTypeVisitor.BuildProperty(field, withHiddenFields: withHidden); var propertyItem = JsonTypeVisitor.BuildProperty(field, components, withHiddenFields: withHidden);
if (propertyItem != null) 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.Collections;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.GenerateJsonSchema namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{ {
public delegate JsonSchema SchemaResolver(string name, Func<JsonSchema> schema); 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 const int MaxDepth = 5;
private static readonly JsonTypeVisitor Instance = new JsonTypeVisitor(); private static readonly JsonTypeVisitor Instance = new JsonTypeVisitor();
public readonly struct Args public sealed record Args(ResolvedComponents Components, SchemaResolver? SchemaResolver, bool WithHiddenFields, int Level = 0);
{
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);
}
}
private JsonTypeVisitor() 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); return field.Accept(Instance, args);
} }
@ -67,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var itemSchema = SchemaBuilder.Object(); 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)) foreach (var nestedField in field.Fields.ForApi(args.WithHiddenFields))
{ {
@ -104,7 +85,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var property = SchemaBuilder.ObjectProperty(); var property = SchemaBuilder.ObjectProperty();
BuildComponent(property, field, field.Properties.SchemaIds, args); BuildComponent(property, field.Properties.SchemaIds, args);
return property; return property;
} }
@ -118,7 +99,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var itemSchema = SchemaBuilder.Object(); var itemSchema = SchemaBuilder.Object();
BuildComponent(itemSchema, field, field.Properties.SchemaIds, args); BuildComponent(itemSchema, field.Properties.SchemaIds, args);
return SchemaBuilder.ArrayProperty(itemSchema); return SchemaBuilder.ArrayProperty(itemSchema);
} }
@ -196,13 +177,13 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return null; 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)); jsonSchema.Properties.Add(Component.Discriminator, SchemaBuilder.StringProperty(isRequired: true));
if (args.SchemaResolver != null) 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 var discriminator = new OpenApiDiscriminator
{ {
@ -215,7 +196,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
var componentSchema = args.SchemaResolver(schemaName, () => var componentSchema = args.SchemaResolver(schemaName, () =>
{ {
var nestedArgs = args.Increment(); var nestedArgs = args with { Level = args.Level + 1 };
var componentSchema = SchemaBuilder.Object(); 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.Infrastructure.Json.Objects;
using Squidex.Text; using Squidex.Text;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
internal sealed class DefaultFieldValueValidatorsFactory : IFieldVisitor<IEnumerable<IValidator>, DefaultFieldValueValidatorsFactory.Args> internal sealed class DefaultFieldValueValidatorsFactory : IFieldVisitor<IEnumerable<IValidator>, DefaultFieldValueValidatorsFactory.Args>
{ {
private static readonly DefaultFieldValueValidatorsFactory Instance = new DefaultFieldValueValidatorsFactory(); private static readonly DefaultFieldValueValidatorsFactory Instance = new DefaultFieldValueValidatorsFactory();
public readonly struct Args public sealed record Args(ValidatorContext Context, ValidatorFactory Factory);
{
public readonly ValidatorContext Context;
public readonly ValidatorFactory Factory;
public Args(ValidatorContext context, ValidatorFactory factory)
{
Context = context;
Factory = factory;
}
}
private DefaultFieldValueValidatorsFactory() 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.Json.Objects;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonError? Error), JsonValueConverter.Args> public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonError? Error), JsonValueConverter.Args>
{ {
private static readonly JsonValueConverter Instance = new JsonValueConverter(); private static readonly JsonValueConverter Instance = new JsonValueConverter();
public readonly struct Args public sealed record Args(IJsonValue Value, IJsonSerializer JsonSerializer, ResolvedComponents Components);
{
public readonly IJsonValue Value;
public readonly IJsonSerializer JsonSerializer;
public Args(IJsonValue value, IJsonSerializer jsonSerializer)
{
Value = value;
JsonSerializer = jsonSerializer;
}
}
private JsonValueConverter() 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(field, nameof(field));
Guard.NotNull(value, nameof(value)); Guard.NotNull(value, nameof(value));
var args = new Args(value, jsonSerializer); var args = new Args(value, jsonSerializer, components);
return field.Accept(Instance, args); 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) 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) 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) 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"))); 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) if (value is JsonArray array)
{ {
@ -207,7 +200,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
for (var i = 0; i < array.Count; i++) 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) if (error != null)
{ {
@ -250,7 +243,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfObjects"))); 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) if (value is not JsonObject obj)
{ {
@ -262,7 +256,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidComponentNoType"))); 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"))); 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;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public sealed class JsonValueValidator : IFieldVisitor<bool, JsonValueValidator.Args> public sealed class JsonValueValidator : IFieldVisitor<bool, JsonValueValidator.Args>
{ {
private static readonly JsonValueValidator Instance = new JsonValueValidator(); private static readonly JsonValueValidator Instance = new JsonValueValidator();
public readonly struct Args public sealed record Args(IJsonValue Value, IJsonSerializer JsonSerializer);
{
public readonly IJsonValue Value;
public readonly IJsonSerializer JsonSerializer;
public Args(IJsonValue value, IJsonSerializer jsonSerializer)
{
Value = value;
JsonSerializer = jsonSerializer;
}
}
private JsonValueValidator() 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 IJsonSerializer JsonSerializer { get; }
public ResolvedComponents Components { get; }
public DomainId ContentId { get; } public DomainId ContentId { get; }
public bool IsOptional { get; private set; } public bool IsOptional { get; private set; }
@ -28,11 +30,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
NamedId<DomainId> appId, NamedId<DomainId> appId,
NamedId<DomainId> schemaId, NamedId<DomainId> schemaId,
Schema schema, Schema schema,
ResolvedComponents components,
DomainId contentId) DomainId contentId)
: base(appId, schemaId, schema) : base(appId, schemaId, schema)
{ {
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
Components = components;
ContentId = contentId; ContentId = contentId;
} }

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
else else
{ {
var (json, error) = JsonValueConverter.ConvertValue(field, jsonValue, context.JsonSerializer); var (json, error) = JsonValueConverter.ConvertValue(field, jsonValue, context.JsonSerializer, context.Components);
if (error != null) 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;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.MongoDb namespace Squidex.Domain.Apps.Entities.MongoDb
{ {
internal sealed class AdaptIdVisitor : TransformVisitor<ClrValue, AdaptIdVisitor.Args> internal sealed class AdaptIdVisitor : TransformVisitor<ClrValue, AdaptIdVisitor.Args>
{ {
private static readonly AdaptIdVisitor Instance = new AdaptIdVisitor(); private static readonly AdaptIdVisitor Instance = new AdaptIdVisitor();
public readonly struct Args public sealed record Args(DomainId AppId);
{
public readonly DomainId AppId;
public Args(DomainId appId)
{
AppId = appId;
}
}
private AdaptIdVisitor() 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) 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 else
{ {

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

@ -9,7 +9,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Caching; using Squidex.Caching;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Rules; 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;
using Squidex.Domain.Apps.Entities.Schemas.Indexes; using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
@ -109,8 +107,6 @@ namespace Squidex.Domain.Apps.Entities
if (schema != null) if (schema != null)
{ {
await ResolveSchemaAsync(appId, schema.SchemaDef);
localCache.Add(cacheKey, schema); localCache.Add(cacheKey, schema);
localCache.Add(SchemaCacheKey(appId, schema.Id), schema); localCache.Add(SchemaCacheKey(appId, schema.Id), schema);
} }
@ -131,8 +127,6 @@ namespace Squidex.Domain.Apps.Entities
if (schema != null) if (schema != null)
{ {
await ResolveSchemaAsync(appId, schema.SchemaDef);
localCache.Add(cacheKey, schema); localCache.Add(cacheKey, schema);
localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), 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); localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema);
} }
foreach (var schema in schemas)
{
await ResolveSchemaAsync(appId, schema.SchemaDef);
}
return schemas; return schemas;
} }
@ -188,59 +177,6 @@ namespace Squidex.Domain.Apps.Entities
return rules.Find(x => x.Id == id); 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) private static string AppCacheKey(DomainId appId)
{ {
return $"APPS_ID_{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;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Assets.Queries namespace Squidex.Domain.Apps.Entities.Assets.Queries
{ {
internal sealed class FilterTagTransformer : AsyncTransformVisitor<ClrValue, FilterTagTransformer.Args> internal sealed class FilterTagTransformer : AsyncTransformVisitor<ClrValue, FilterTagTransformer.Args>
{ {
private static readonly FilterTagTransformer Instance = new FilterTagTransformer(); private static readonly FilterTagTransformer Instance = new FilterTagTransformer();
public readonly struct Args public sealed record Args(DomainId AppId, ITagService TagService);
{
public readonly DomainId AppId;
public readonly ITagService TagService;
public Args(DomainId appId, ITagService tagService)
{
AppId = appId;
TagService = tagService;
}
}
private FilterTagTransformer() 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.App.NamedId(),
context.Schema.NamedId(), context.Schema.NamedId(),
context.SchemaDef, context.SchemaDef,
context.Components,
context.ContentId) context.ContentId)
.Optimized(optimize).AsPublishing(published); .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 DomainId ContentId { get; init; }
public ResolvedComponents Components { get; init; }
public Func<IContentEntity> ContentProvider { get; init; } public Func<IContentEntity> ContentProvider { get; init; }
public IContentEntity Content public IContentEntity Content
@ -69,10 +71,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
throw new DomainObjectNotFoundException(command.SchemaId.Id.ToString()); throw new DomainObjectNotFoundException(command.SchemaId.Id.ToString());
} }
var components = await appProvider.GetComponentsAsync(schema);
return new OperationContext(services) return new OperationContext(services)
{ {
App = app, App = app,
Actor = command.Actor, Actor = command.Actor,
Components = components,
ContentProvider = snapshot, ContentProvider = snapshot,
ContentId = command.ContentId, ContentId = command.ContentId,
Schema = schema, 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) foreach (var schemaInfo in schemaInfos)
{ {
var contentType = new ContentGraphType(this, schemaInfo); var contentType = new ContentGraphType(schemaInfo);
contentTypes[schemaInfo] = contentType; contentTypes[schemaInfo] = contentType;
contentResultTypes[schemaInfo] = new ContentResultGraphType(contentType, schemaInfo); contentResultTypes[schemaInfo] = new ContentResultGraphType(contentType, schemaInfo);
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
foreach (var schemaInfo in allSchemas) foreach (var schemaInfo in allSchemas)
{ {
var componentType = new ComponentGraphType(this, schemaInfo); var componentType = new ComponentGraphType(schemaInfo);
componentTypes[schemaInfo] = componentType; 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> internal sealed class ComponentGraphType : ObjectGraphType<JsonObject>
{ {
public ComponentGraphType(Builder builder, SchemaInfo schemaInfo) public ComponentGraphType(SchemaInfo schemaInfo)
{ {
Name = schemaInfo.ComponentType; Name = schemaInfo.ComponentType;
Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
IsTypeOf = CheckType(schemaInfo.Schema.Id.ToString()); IsTypeOf = CheckType(schemaInfo.Schema.Id.ToString());
} }
public void Initialize(Builder builder, SchemaInfo schemaInfo) public void Initialize(Builder builder, SchemaInfo schemaInfo)
{ {
Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
AddField(ContentFields.SchemaId); AddField(ContentFields.SchemaId);
foreach (var fieldInfo in schemaInfo.Fields) 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; private readonly DomainId schemaId;
public ContentGraphType(Builder builder, SchemaInfo schemaInfo) public ContentGraphType(SchemaInfo schemaInfo)
{ {
schemaId = schemaInfo.Schema.Id; 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) public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)
{ {
Name = schemaInfo.ContentType; Name = schemaInfo.ContentType;
AddField(ContentFields.Id); AddField(ContentFields.Id);
@ -83,6 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
AddReferencingQueries(builder, other); AddReferencingQueries(builder, other);
} }
AddResolvedInterface(builder.SharedTypes.ContentInterface); AddResolvedInterface(builder.SharedTypes.ContentInterface);
Description = $"The structure of a {schemaInfo.DisplayName} content type."; 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -20,18 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public sealed class ContentEnricher : IContentEnricher public sealed class ContentEnricher : IContentEnricher
{ {
private readonly IEnumerable<IContentEnricherStep> steps; private readonly IEnumerable<IContentEnricherStep> steps;
private readonly Lazy<IContentQueryService> contentQuery; private readonly IAppProvider appProvider;
private IContentQueryService ContentQuery public ContentEnricher(IEnumerable<IContentEnricherStep> steps, IAppProvider appProvider)
{
get => contentQuery.Value;
}
public ContentEnricher(IEnumerable<IContentEnricherStep> steps, Lazy<IContentQueryService> contentQuery)
{ {
this.steps = steps; this.steps = steps;
this.contentQuery = contentQuery; this.appProvider = appProvider;
} }
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, bool cloneData, Context context, 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) 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) 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 JsonSchema genericJsonSchema = ContentJsonSchemaBuilder.BuildSchema("Content", null, false, true);
private readonly IMemoryCache cache; private readonly IMemoryCache cache;
private readonly IJsonSerializer jsonSerializer; private readonly IJsonSerializer jsonSerializer;
private readonly IAppProvider appprovider;
private readonly ITextIndex textIndex; private readonly ITextIndex textIndex;
private readonly ContentOptions options; 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.jsonSerializer = jsonSerializer;
this.appprovider = appprovider;
this.textIndex = textIndex; this.textIndex = textIndex;
this.cache = cache; this.cache = cache;
this.options = options.Value; this.options = options.Value;
@ -58,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
using (Profiler.TraceMethod<ContentQueryParser>()) using (Profiler.TraceMethod<ContentQueryParser>())
{ {
var query = ParseClrQuery(context, q, schema); var query = await ParseClrQueryAsync(context, q, schema);
await TransformFilterAsync(query, context, 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; var query = q.Query;
if (!string.IsNullOrWhiteSpace(q.JsonQueryString)) if (!string.IsNullOrWhiteSpace(q.JsonQueryString))
{ {
query = ParseJson(context, schema, q.JsonQueryString); query = ParseJson(context, schema, q.JsonQueryString, components);
} }
else if (q?.JsonQuery != null) else if (q?.JsonQuery != null)
{ {
query = ParseJson(context, schema, q.JsonQuery); query = ParseJson(context, schema, q.JsonQuery, components);
} }
else if (!string.IsNullOrWhiteSpace(q?.ODataQuery)) else if (!string.IsNullOrWhiteSpace(q?.ODataQuery))
{ {
query = ParseOData(context, schema, q.ODataQuery); query = ParseOData(context, schema, q.ODataQuery, components);
} }
return query; 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); 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); 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 try
{ {
var model = BuildEdmModel(context, schema); var model = BuildEdmModel(context, schema, components);
return model.ParseQuery(odata).ToQuery(); 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) if (schema == null)
{ {
@ -209,13 +223,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
entry.AbsoluteExpirationRelativeToNow = CacheTime; entry.AbsoluteExpirationRelativeToNow = CacheTime;
return BuildJsonSchema(schema.SchemaDef, context.App, context.IsFrontendClient); return BuildJsonSchema(schema.SchemaDef, context.App, components, context.IsFrontendClient);
}); });
return result; return result;
} }
private IEdmModel BuildEdmModel(Context context, ISchemaEntity? schema) private IEdmModel BuildEdmModel(Context context, ISchemaEntity? schema,
ResolvedComponents components)
{ {
if (schema == null) if (schema == null)
{ {
@ -228,20 +243,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
entry.AbsoluteExpirationRelativeToNow = CacheTime; entry.AbsoluteExpirationRelativeToNow = CacheTime;
return BuildEdmModel(schema.SchemaDef, context.App, context.IsFrontendClient); return BuildEdmModel(schema.SchemaDef, context.App, components, context.IsFrontendClient);
}); });
return result; 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); 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(); var model = new EdmModel();
@ -272,7 +289,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
return (result, true); 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); 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.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Contents.Queries namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
internal sealed class GeoQueryTransformer : AsyncTransformVisitor<ClrValue, GeoQueryTransformer.Args> internal sealed class GeoQueryTransformer : AsyncTransformVisitor<ClrValue, GeoQueryTransformer.Args>
{ {
public static readonly GeoQueryTransformer Instance = new GeoQueryTransformer(); public static readonly GeoQueryTransformer Instance = new GeoQueryTransformer();
public readonly struct Args public sealed record Args(Context Context, ISchemaEntity Schema, ITextIndex TextIndex);
{
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;
}
}
private GeoQueryTransformer() private GeoQueryTransformer()
{ {

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

@ -8,12 +8,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Queries 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 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;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.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 public sealed class ConvertData : IContentEnricherStep
{ {
private readonly IUrlGenerator urlGenerator; private readonly IUrlGenerator urlGenerator;
private readonly IJsonSerializer jsonSerializer;
private readonly IAssetRepository assetRepository; private readonly IAssetRepository assetRepository;
private readonly IContentRepository contentRepository; private readonly IContentRepository contentRepository;
private readonly FieldConverter excludedChangedField; private readonly FieldConverter excludedChangedField;
private readonly FieldConverter excludedChangedValue;
private readonly FieldConverter excludedHiddenField; private readonly FieldConverter excludedHiddenField;
private readonly FieldConverter excludedHiddenValue;
public ConvertData(IUrlGenerator urlGenerator, IJsonSerializer jsonSerializer, public ConvertData(IUrlGenerator urlGenerator, IJsonSerializer jsonSerializer,
IAssetRepository assetRepository, IContentRepository contentRepository) IAssetRepository assetRepository, IContentRepository contentRepository)
{ {
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
this.jsonSerializer = jsonSerializer;
this.assetRepository = assetRepository; this.assetRepository = assetRepository;
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
excludedChangedField = FieldConverters.ExcludeChangedTypes(jsonSerializer); excludedChangedField = FieldConverters.ExcludeChangedTypes(jsonSerializer);
excludedChangedValue = FieldConverters.ForValues(ValueConverters.ExcludeChangedTypes(jsonSerializer));
excludedHiddenField = FieldConverters.ExcludeHidden; excludedHiddenField = FieldConverters.ExcludeHidden;
excludedHiddenValue = FieldConverters.ForValues(ValueConverters.ExcludeHidden);
} }
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas, 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 referenceCleaner = await CleanReferencesAsync(context, contents, schemas, ct);
var converters = GenerateConverters(context, referenceCleaner).ToArray();
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{ {
ct.ThrowIfCancellationRequested(); 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) 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)) 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) 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; return result;
} }
private IEnumerable<FieldConverter> GenerateConverters(Context context, ValueConverter? cleanReferences) private IEnumerable<FieldConverter> GenerateConverters(Context context, ResolvedComponents components, ValueConverter? cleanReferences)
{ {
if (!context.IsFrontendClient) if (!context.IsFrontendClient)
{ {
yield return excludedHiddenField; yield return excludedHiddenField;
yield return excludedHiddenValue; yield return FieldConverters.ForValues(components, ValueConverters.ExcludeHidden);
} }
yield return excludedChangedField; yield return excludedChangedField;
yield return excludedChangedValue; yield return FieldConverters.ForValues(components, ValueConverters.ExcludeChangedTypes(jsonSerializer));
if (cleanReferences != null) if (cleanReferences != null)
{ {
yield return FieldConverters.ForValues(cleanReferences); yield return FieldConverters.ForValues(components, cleanReferences);
} }
yield return FieldConverters.ResolveInvariant(context.App.Languages); 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); 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(); ct.ThrowIfCancellationRequested();
var schema = await schemas(group.Key); var (schema, _) = await schemas(group.Key);
foreach (var content in group) 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(); ct.ThrowIfCancellationRequested();
var schema = await schemas(group.Key); var (schema, _) = await schemas(group.Key);
var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged(); 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)) 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); var assets = await GetAssetsAsync(context, ids, ct);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) 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()) 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) foreach (var (partitionKey, partitionValue) in fieldData)
{ {
var referencedAsset = var referencedAsset =
field.GetReferencedIds(partitionValue) field.GetReferencedIds(partitionValue, components)
.Select(x => assets[x]) .Select(x => assets[x])
.SelectMany(x => x) .SelectMany(x => x)
.FirstOrDefault(); .FirstOrDefault();
@ -133,11 +134,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
return assets.ToLookup(x => x.Id); 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) 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)) 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); var references = await GetReferencesAsync(context, ids, ct);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) 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, private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, ResolvedComponents components,
ProvideSchema schemas) IEnumerable<ContentEntity> contents, ILookup<DomainId, IEnrichedContentEntity> references, ProvideSchema schemas)
{ {
var formatted = new Dictionary<IContentEntity, JsonObject>(); var formatted = new Dictionary<IContentEntity, JsonObject>();
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
foreach (var (partition, partitionValue) in fieldData) foreach (var (partition, partitionValue) in fieldData)
{ {
var referencedContents = var referencedContents =
field.GetReferencedIds(partitionValue) field.GetReferencedIds(partitionValue, components)
.Select(x => references[x]) .Select(x => references[x])
.SelectMany(x => x) .SelectMany(x => x)
.ToList(); .ToList();
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{ {
var reference = referencedContents[0]; 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(referencedSchema.UniqueId, referencedSchema.Version);
requestCache.AddDependency(reference.UniqueId, reference.Version); requestCache.AddDependency(reference.UniqueId, reference.Version);
@ -138,11 +138,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
return value; 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) 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)) 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; 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.Json.Objects;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Infrastructure.Queries.Json namespace Squidex.Infrastructure.Queries.Json
{ {
public sealed class JsonFilterVisitor : FilterNodeVisitor<FilterNode<ClrValue>, IJsonValue, JsonFilterVisitor.Args> public sealed class JsonFilterVisitor : FilterNodeVisitor<FilterNode<ClrValue>, IJsonValue, JsonFilterVisitor.Args>
{ {
private static readonly JsonFilterVisitor Instance = new JsonFilterVisitor(); private static readonly JsonFilterVisitor Instance = new JsonFilterVisitor();
public struct Args public sealed record Args(JsonSchema Schema, List<string> Errors);
{
public readonly List<string> Errors;
public JsonSchema Schema;
public Args(JsonSchema schema, List<string> errors)
{
Schema = schema;
Errors = errors;
}
}
private JsonFilterVisitor() 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 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"); return Content(openApiDocument.ToJson(), "application/json");
} }
@ -77,7 +77,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
var schemas = await appProvider.GetSchemasAsync(AppId); 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"); 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; return builder;
} }
public OperationsBuilder Schema(Schema schema, bool flat) public OperationsBuilder Schema(Schema schema, ResolvedComponents components, bool flat)
{ {
var typeName = schema.TypeName(); var typeName = schema.TypeName();
@ -87,7 +87,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
var dataSchema = ResolveSchema($"{typeName}DataDto", () => var dataSchema = ResolveSchema($"{typeName}DataDto", () =>
{ {
return schema.BuildDynamicJsonSchema(ResolveSchema); return schema.BuildDynamicJsonSchema(ResolveSchema, components);
}); });
var contentSchema = ResolveSchema($"{typeName}ContentDto", () => var contentSchema = ResolveSchema($"{typeName}ContentDto", () =>
@ -98,7 +98,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{ {
contentDataSchema = ResolveSchema($"{typeName}FlatDataDto", () => 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using NJsonSchema; using NJsonSchema;
using NSwag; using NSwag;
using NSwag.Generation; using NSwag.Generation;
using NSwag.Generation.Processors.Contexts; using NSwag.Generation.Processors.Contexts;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Hosting; using Squidex.Hosting;
@ -25,24 +27,27 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{ {
public sealed class SchemasOpenApiGenerator public sealed class SchemasOpenApiGenerator
{ {
private readonly IAppProvider appProvider;
private readonly IUrlGenerator urlGenerator; private readonly IUrlGenerator urlGenerator;
private readonly OpenApiDocumentGeneratorSettings schemaSettings; private readonly OpenApiDocumentGeneratorSettings schemaSettings;
private readonly OpenApiSchemaGenerator schemaGenerator; private readonly OpenApiSchemaGenerator schemaGenerator;
private readonly IRequestCache requestCache; private readonly IRequestCache requestCache;
public SchemasOpenApiGenerator( public SchemasOpenApiGenerator(
IAppProvider appProvider,
IUrlGenerator urlGenerator, IUrlGenerator urlGenerator,
OpenApiDocumentGeneratorSettings schemaSettings, OpenApiDocumentGeneratorSettings schemaSettings,
OpenApiSchemaGenerator schemaGenerator, OpenApiSchemaGenerator schemaGenerator,
IRequestCache requestCache) IRequestCache requestCache)
{ {
this.appProvider = appProvider;
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
this.schemaSettings = schemaSettings; this.schemaSettings = schemaSettings;
this.schemaGenerator = schemaGenerator; this.schemaGenerator = schemaGenerator;
this.requestCache = requestCache; 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); var document = CreateApiDocument(httpContext, app);
@ -68,7 +73,9 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
foreach (var schema in validSchemas) 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()); 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() public void Should_serialize_and_deserialize_schema()
{ {
var schemaSource = var schemaSource =
TestUtils.MixedSchema(SchemaType.Singleton, false) TestUtils.MixedSchema(SchemaType.Singleton)
.ChangeCategory("Category") .ChangeCategory("Category")
.SetFieldRules(FieldRule.Hide("2")) .SetFieldRules(FieldRule.Hide("2"))
.SetFieldsInLists("field2") .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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -17,6 +18,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
public class ContentConversionTests public class ContentConversionTests
{ {
private readonly Schema schema; private readonly Schema schema;
private readonly ResolvedComponents components;
public ContentConversionTests() public ContentConversionTests()
{ {
@ -30,8 +32,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
.AddArray(6, "array", Partitioning.Invariant, a => a .AddArray(6, "array", Partitioning.Invariant, a => a
.AddAssets(31, "nested")); .AddAssets(31, "nested"));
schema.FieldsById[1].SetResolvedSchema(DomainId.Empty, schema); components = new ResolvedComponents(new Dictionary<DomainId, Schema>
schema.FieldsById[2].SetResolvedSchema(DomainId.Empty, schema); {
[DomainId.Empty] = schema
});
} }
[Fact] [Fact]
@ -113,9 +117,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
JsonValue.Object())) JsonValue.Object()))
.Add(Component.Discriminator, DomainId.Empty)))); .Add(Component.Discriminator, DomainId.Empty))));
var converter = new ValueConverter((data, field, parent) => field.Name != "assets1" ? null : data);
var actual = var actual =
source.Convert(schema, source.Convert(schema,
FieldConverters.ForValues((data, field, parent) => field.Name != "assets1" ? null : data)); FieldConverters.ForValues(components, converter));
Assert.Equal(expected, actual); 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() new ContentFieldData()
.AddInvariant(JsonValue.Object()); .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(); 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 public class ReferenceExtractionTests
{ {
private readonly Schema schema; private readonly Schema schema;
private readonly ResolvedComponents components;
public ReferenceExtractionTests() public ReferenceExtractionTests()
{ {
@ -35,11 +36,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
.AddComponent(32, "nestedComponent") .AddComponent(32, "nestedComponent")
.AddComponents(33, "nestedComponents")); .AddComponents(33, "nestedComponents"));
schema.FieldsById[1].SetResolvedSchema(DomainId.Empty, schema); components = new ResolvedComponents(new Dictionary<DomainId, Schema>
schema.FieldsById[2].SetResolvedSchema(DomainId.Empty, schema); {
[DomainId.Empty] = schema
((IArrayField)schema.FieldsById[6]).FieldsById[32].SetResolvedSchema(DomainId.Empty, schema); });
((IArrayField)schema.FieldsById[6]).FieldsById[33].SetResolvedSchema(DomainId.Empty, schema);
} }
[Fact] [Fact]
@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var ids = new HashSet<DomainId>(); var ids = new HashSet<DomainId>();
input.AddReferencedIds(schema, ids); input.AddReferencedIds(schema, ids, components);
Assert.Equal(new[] { id1, id2 }, ids); Assert.Equal(new[] { id1, id2 }, ids);
} }
@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var ids = new HashSet<DomainId>(); var ids = new HashSet<DomainId>();
input.AddReferencedIds(schema, ids, 1); input.AddReferencedIds(schema, ids, components, 1);
Assert.Equal(new[] { id1 }, ids); Assert.Equal(new[] { id1 }, ids);
} }
@ -193,7 +193,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
.Add(Component.Discriminator, DomainId.Empty)))); .Add(Component.Discriminator, DomainId.Empty))));
var converter = var converter =
FieldConverters.ForValues( FieldConverters.ForValues(components,
ValueReferencesConverter.CleanReferences(new HashSet<DomainId> { id2 })); ValueReferencesConverter.CleanReferences(new HashSet<DomainId> { id2 }));
var actual = source.Convert(schema, converter); 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 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); Assert.Empty(result);
} }
@ -225,7 +225,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
JsonValue.Object() JsonValue.Object()
.Add(field.Name, CreateValue(id1, id2))); .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); Assert.Equal(new[] { id1, id2 }, result);
} }
@ -234,7 +234,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))] [MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_item_is_invalid(IField field) 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); Assert.Empty(result);
} }
@ -243,7 +243,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))] [MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_is_empty(IField field) 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); Assert.Empty(result);
} }
@ -252,7 +252,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))] [MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_is_json_null(IField field) 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); Assert.Empty(result);
} }
@ -261,7 +261,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
[MemberData(nameof(ReferencingFields))] [MemberData(nameof(ReferencingFields))]
public void Should_return_empty_list_from_field_if_value_is_null(IField field) 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); Assert.Empty(result);
} }
@ -275,7 +275,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var value = CreateValue(id1, id2); var value = CreateValue(id1, id2);
var result = field.GetReferencedIds(value); var result = field.GetReferencedIds(value, components);
Assert.Equal(new HashSet<DomainId> { id1, id2 }, result); 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 Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Xunit; using Xunit;
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateEdmSchema
var edmModel = var edmModel =
TestUtils.MixedSchema() TestUtils.MixedSchema()
.BuildEdmType(true, languagesConfig.ToResolver(), typeFactory); .BuildEdmType(true, languagesConfig.ToResolver(), typeFactory, ResolvedComponents.Empty);
Assert.NotNull(edmModel); 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 public class JsonSchemaTests
{ {
private const int MaxDepth = 5;
private readonly Schema schema = TestUtils.MixedSchema(); private readonly Schema schema = TestUtils.MixedSchema();
[Fact] [Fact]
@ -26,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
{ {
var languagesConfig = LanguagesConfig.English.Set(Language.DE); var languagesConfig = LanguagesConfig.English.Set(Language.DE);
var jsonSchema = schema.BuildJsonSchema(languagesConfig.ToResolver()); var jsonSchema = schema.BuildJsonSchema(languagesConfig.ToResolver(), ResolvedComponents.Empty);
CheckFields(jsonSchema); CheckFields(jsonSchema);
} }
@ -36,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
{ {
var schemaResolver = new SchemaResolver((name, action) => action()); var schemaResolver = new SchemaResolver((name, action) => action());
var jsonSchema = schema.BuildDynamicJsonSchema(schemaResolver); var jsonSchema = schema.BuildDynamicJsonSchema(schemaResolver, ResolvedComponents.Empty);
CheckFields(jsonSchema); CheckFields(jsonSchema);
} }
@ -51,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema
return action(); return action();
}); });
var jsonSchema = schema.BuildFlatJsonSchema(schemaResolver); var jsonSchema = schema.BuildFlatJsonSchema(schemaResolver, ResolvedComponents.Empty);
CheckFields(jsonSchema); 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] [Fact]
public void Should_instantiate_field() public void Should_instantiate_field()
{ {
var (_, sut) = Field(new ComponentFieldProperties()); var (_, sut, _) = Field(new ComponentFieldProperties());
Assert.Equal("my-component", sut.Name); Assert.Equal("my-component", sut.Name);
} }
@ -32,9 +32,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_not_add_error_if_component_is_null_and_valid() 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); Assert.Empty(errors);
} }
@ -42,9 +42,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_not_add_error_if_component_is_valid() 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); Assert.Empty(errors);
} }
@ -52,9 +52,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_component_is_required() 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( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); new[] { "Field is required." });
@ -63,9 +63,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_component_value_is_required() 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( errors.Should().BeEquivalentTo(
new[] { "component-field: Field is required." }); new[] { "component-field: Field is required." });
@ -74,9 +74,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_is_not_valid() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid json object, expected object with 'schemaId' field." }); new[] { "Invalid json object, expected object with 'schemaId' field." });
@ -85,9 +85,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_has_no_discriminator() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid component. No 'schemaId' field found." }); new[] { "Invalid component. No 'schemaId' field found." });
@ -96,9 +96,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_has_invalid_discriminator() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." }); new[] { "Invalid component. Cannot find schema." });
@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
return obj; 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 = var schema =
new Schema("my-component") new Schema("my-component")
@ -127,11 +127,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
var id = DomainId.NewGuid(); var id = DomainId.NewGuid();
var field = var field = Fields.Component(1, "my-component", Partitioning.Invariant, properties);
Fields.Component(1, "my-component", Partitioning.Invariant, properties)
.SetResolvedSchema(id, schema);
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] [Fact]
public void Should_instantiate_field() public void Should_instantiate_field()
{ {
var (_, sut) = Field(new ComponentsFieldProperties()); var (_, sut, _) = Field(new ComponentsFieldProperties());
Assert.Equal("my-components", sut.Name); Assert.Equal("my-components", sut.Name);
} }
@ -32,9 +32,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_not_add_error_if_components_are_null_and_valid() 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); Assert.Empty(errors);
} }
@ -42,9 +42,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_not_add_error_if_components_is_valid() 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); Assert.Empty(errors);
} }
@ -52,9 +52,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_not_add_error_if_number_of_components_is_equal_to_min_and_max_components() 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); Assert.Empty(errors);
} }
@ -62,9 +62,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_components_are_required() 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( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); new[] { "Field is required." });
@ -73,9 +73,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_components_value_is_required() 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( errors.Should().BeEquivalentTo(
new[] { "[1].component-field: Field is required." }); new[] { "[1].component-field: Field is required." });
@ -84,9 +84,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_is_not_valid() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of objects." }); new[] { "Invalid json type, expected array of objects." });
@ -95,9 +95,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_component_is_not_valid() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid json object, expected object with 'schemaId' field." }); new[] { "Invalid json object, expected object with 'schemaId' field." });
@ -106,9 +106,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_component_has_no_discriminator() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid component. No 'schemaId' field found." }); new[] { "Invalid component. No 'schemaId' field found." });
@ -117,9 +117,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_has_invalid_discriminator() 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( errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." }); new[] { "Invalid component. Cannot find schema." });
@ -128,9 +128,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_has_not_enough_components() 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( errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." }); new[] { "Must have at least 3 item(s)." });
@ -139,9 +139,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
[Fact] [Fact]
public async Task Should_add_error_if_value_has_too_much_components() 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( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); new[] { "Must not have more than 1 item(s)." });
@ -168,7 +168,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
return result; 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 = var schema =
new Schema("my-component") new Schema("my-component")
@ -177,11 +177,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
var id = DomainId.NewGuid(); var id = DomainId.NewGuid();
var field = var field = Fields.Components(1, "my-components", Partitioning.Invariant, properties);
Fields.Components(1, "my-components", Partitioning.Invariant, properties)
.SetResolvedSchema(id, schema);
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, Schema? schema = null,
ValidationMode mode = ValidationMode.Default, ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null, 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)); return validator.ValidateAsync(value, context, CreateFormatter(errors));
} }
@ -43,9 +45,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
ValidationMode mode = ValidationMode.Default, ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null, ValidationUpdater? updater = null,
IValidatorsFactory? factory = 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); var validator = new ValidatorBuilder(factory, context).ValueValidator(field);
@ -57,9 +61,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
ValidationMode mode = ValidationMode.Default, ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null, ValidationUpdater? updater = null,
IValidatorsFactory? factory = 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); var validator = new ValidatorBuilder(factory, context).ContentValidator(partitionResolver);
@ -76,9 +82,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
ValidationMode mode = ValidationMode.Default, ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null, ValidationUpdater? updater = null,
IValidatorsFactory? factory = 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); var validator = new ValidatorBuilder(factory, context).ContentValidator(partitionResolver);
@ -109,14 +117,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
Schema? schema = null, Schema? schema = null,
ValidationMode mode = ValidationMode.Default, ValidationMode mode = ValidationMode.Default,
ValidationUpdater? updater = null, ValidationUpdater? updater = null,
ValidationAction action = ValidationAction.Upsert) ValidationAction action = ValidationAction.Upsert,
ResolvedComponents? components = null,
DomainId? contentId = null)
{ {
var context = new ValidationContext( var context = new ValidationContext(
TestUtils.DefaultSerializer, TestUtils.DefaultSerializer,
AppId, AppId,
SchemaId, SchemaId,
schema ?? new Schema(SchemaId.Name), schema ?? new Schema(SchemaId.Name),
DomainId.NewGuid()); components ?? ResolvedComponents.Empty,
contentId ?? DomainId.NewGuid());
context = context.WithMode(mode).WithAction(action); 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); 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(); var componentId = DomainId.NewGuid();
@ -135,12 +135,6 @@ namespace Squidex.Domain.Apps.Core.TestHelpers
.DisableField(212, 101) .DisableField(212, 101)
.LockField(105); .LockField(105);
if (withMetadata)
{
schema.FieldsById[114].SetResolvedSchema(componentId, schema);
schema.FieldsById[115].SetResolvedSchema(componentId, schema);
}
return 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 public class ContentEnricherTests
{ {
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly ISchemaEntity schema; private readonly ISchemaEntity schema;
private readonly Context requestContext; private readonly Context requestContext;
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); 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)) 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); schema = Mocks.Schema(appId, schemaId);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, schemaId.Id.ToString())) A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(schema); .Returns(schema);
} }
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var step1 = A.Fake<IContentEnricherStep>(); var step1 = A.Fake<IContentEnricherStep>();
var step2 = 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); await sut.EnrichAsync(source, requestContext, default);
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var step1 = A.Fake<IContentEnricherStep>(); var step1 = A.Fake<IContentEnricherStep>();
var step2 = 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); await sut.EnrichAsync(source, false, requestContext, default);
@ -109,14 +109,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var step1 = new ResolveSchema(); var step1 = new ResolveSchema();
var step2 = 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); await sut.EnrichAsync(source, false, requestContext, default);
Assert.Same(schema, step1.Schema); Assert.Same(schema, step1.Schema);
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(); .MustHaveHappenedOnceExactly();
} }
@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
var source = CreateContent(new ContentData()); 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); 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 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); 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 public class ContentQueryParserTests
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly ITextIndex textIndex = A.Fake<ITextIndex>(); private readonly ITextIndex textIndex = A.Fake<ITextIndex>();
private readonly ISchemaEntity schema; private readonly ISchemaEntity schema;
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); 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())); 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] [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")); .AddAssets(31, "nested"));
schema = Mocks.Schema(appId, schemaId, schemaDef); 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); 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.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; 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)); requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
schema = Mocks.Schema(appId, schemaId); schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema); schemaProvider = x => Task.FromResult((schema, ResolvedComponents.Empty));
sut = new EnrichForCaching(requestCache); 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.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
@ -26,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public EnrichWithSchemaTests() public EnrichWithSchemaTests()
{ {
schema = Mocks.Schema(appId, schemaId); schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema); schemaProvider = x => Task.FromResult((schema, ResolvedComponents.Empty));
sut = new EnrichWithSchema(); 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) if (x == schemaId.Id)
{ {
return Task.FromResult(Mocks.Schema(appId, schemaId, schemaDef)); return Task.FromResult((Mocks.Schema(appId, schemaId, schemaDef), ResolvedComponents.Empty));
} }
else 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) 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) 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) else if (x == refSchemaId2.Id)
{ {
return Task.FromResult(Mocks.Schema(appId, refSchemaId2, refSchemaDef)); return Task.FromResult((Mocks.Schema(appId, refSchemaId2, refSchemaDef), ResolvedComponents.Empty));
} }
else 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) 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) else if (x == schemaWithScriptId.Id)
{ {
return Task.FromResult(Mocks.Schema(appId, schemaWithScriptId, schemaDefWithScript)); return Task.FromResult((Mocks.Schema(appId, schemaWithScriptId, schemaDefWithScript), ResolvedComponents.Empty));
} }
else else
{ {

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

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

Loading…
Cancel
Save