diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs index 749ba26a3..bf52c5824 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs @@ -14,7 +14,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Core.ValidateContent { - public delegate Task> CheckContents(Guid schemaId, FilterNode filter); + public delegate Task> CheckContents(Guid schemaId, FilterNode filter); public delegate Task> CheckAssets(IEnumerable ids); @@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional); } - public Task> GetContentIdsAsync(Guid validatedSchemaId, FilterNode filter) + public Task> GetContentIdsAsync(Guid validatedSchemaId, FilterNode filter) { return checkContent(validatedSchemaId, filter); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs index fa388d365..91066eab2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class ReferencesValidator : IValidator { - private static readonly IReadOnlyList Path = new List { "Id" }; + private static readonly PropertyPath Path = "Id"; private readonly Guid schemaId; @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (value is ICollection contentIds) { - var filter = new FilterComparison(Path, FilterOperator.In, new FilterValue(contentIds.ToList())); + var filter = ClrFilter.In(Path, contentIds.ToList()); var foundIds = await context.GetContentIdsAsync(schemaId, filter); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs index 06c4c6d39..6717f242b 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs @@ -20,15 +20,15 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Key))) { - FilterNode filter = null; + FilterNode filter = null; if (value is string s) { - filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(s)); + filter = ClrFilter.Eq(Path(context), s); } else if (value is double d) { - filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(d)); + filter = ClrFilter.Eq(Path(context), d); } if (filter != null) diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index b7ccaf0ed..b09261e24 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets ct); } - public async Task> QueryAsync(Guid appId, Query query) + public async Task> QueryAsync(Guid appId, ClrQuery query) { using (Profiler.TraceMethod("QueryAsyncByQuery")) { diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs index a052f957d..32087f5a4 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs @@ -20,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors { private static readonly FilterDefinitionBuilder Filter = Builders.Filter; - public static Query AdjustToModel(this Query query) + public static ClrQuery AdjustToModel(this ClrQuery query) { if (query.Filter != null) { - query.Filter = PascalCasePathConverter.Transform(query.Filter); + query.Filter = PascalCasePathConverter.Transform(query.Filter); } query.Sort = query.Sort @@ -37,22 +37,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors return query; } - public static IFindFluent AssetSort(this IFindFluent cursor, Query query) + public static IFindFluent AssetSort(this IFindFluent cursor, ClrQuery query) { return cursor.Sort(query.BuildSort()); } - public static IFindFluent AssetTake(this IFindFluent cursor, Query query) + public static IFindFluent AssetTake(this IFindFluent cursor, ClrQuery query) { return cursor.Take(query); } - public static IFindFluent AssetSkip(this IFindFluent cursor, Query query) + public static IFindFluent AssetSkip(this IFindFluent cursor, ClrQuery query) { return cursor.Skip(query); } - public static FilterDefinition BuildFilter(this Query query, Guid appId) + public static FilterDefinition BuildFilter(this ClrQuery query, Guid appId) { var filters = new List> { diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index 9424333c0..005a09466 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return "State_Contents"; } - public async Task> QueryAsync(ISchemaEntity schema, Query query, List ids, Status[] status, bool inDraft, bool includeDraft = true) + public async Task> QueryAsync(ISchemaEntity schema, ClrQuery query, List ids, Status[] status, bool inDraft, bool includeDraft = true) { try { @@ -169,7 +169,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents }); } - public async Task> QueryIdsAsync(ISchemaEntity schema, FilterNode filterNode) + public async Task> QueryIdsAsync(ISchemaEntity schema, FilterNode filterNode) { var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id); diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 51e7ca5a9..e6c5c2f5f 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return contents.InitializeAsync(ct); } - public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, Query query, bool includeDraft = true) + public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, ClrQuery query, bool includeDraft = true) { Guard.NotNull(app, nameof(app)); Guard.NotNull(schema, nameof(schema)); @@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents } } - public async Task> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode) + public async Task> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode) { using (Profiler.TraceMethod()) { diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs index 396971e84..3de2734e8 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs @@ -13,35 +13,35 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { - internal sealed class AdaptionVisitor : TransformVisitor + internal sealed class AdaptionVisitor : TransformVisitor { - private readonly Func, IReadOnlyList> pathConverter; + private readonly Func pathConverter; - public AdaptionVisitor(Func, IReadOnlyList> pathConverter) + public AdaptionVisitor(Func pathConverter) { this.pathConverter = pathConverter; } - public override FilterNode Visit(FilterComparison nodeIn) + public override FilterNode Visit(CompareFilter nodeIn) { - FilterComparison result; + CompareFilter result; - var value = nodeIn.Rhs.Value; + var value = nodeIn.Value.Value; if (value is Instant && - !string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) && - !string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase)) + !string.Equals(nodeIn.Path[0], "mt", StringComparison.OrdinalIgnoreCase) && + !string.Equals(nodeIn.Path[0], "ct", StringComparison.OrdinalIgnoreCase)) { - result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString())); + result = new CompareFilter(pathConverter(nodeIn.Path), nodeIn.Operator, value.ToString()); } else { - result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs); + result = new CompareFilter(pathConverter(nodeIn.Path), nodeIn.Operator, nodeIn.Value); } - if (result.Lhs.Count == 1 && result.Lhs[0] == "_id" && result.Rhs.Value is List guidList) + if (result.Path.Count == 1 && result.Path[0] == "_id" && result.Value.Value is List guidList) { - result = new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(guidList.Select(x => x.ToString()).ToList())); + result = new CompareFilter(nodeIn.Path, nodeIn.Operator, guidList.Select(x => x.ToString()).ToList()); } return result; diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs index 1dd0b1500..f520635f8 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors typeof(MongoContentEntity).GetProperties() .ToDictionary(x => x.Name, x => x.GetCustomAttribute()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase); - public static Query AdjustToModel(this Query query, Schema schema, bool useDraft) + public static ClrQuery AdjustToModel(this ClrQuery query, Schema schema, bool useDraft) { var pathConverter = PathConverter(schema, useDraft); @@ -42,14 +42,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors return query; } - public static FilterNode AdjustToModel(this FilterNode filterNode, Schema schema, bool inDraft) + public static FilterNode AdjustToModel(this FilterNode filterNode, Schema schema, bool inDraft) { var pathConverter = PathConverter(schema, inDraft); return filterNode.Accept(new AdaptionVisitor(pathConverter)); } - private static Func, IReadOnlyList> PathConverter(Schema schema, bool inDraft) + private static Func PathConverter(Schema schema, bool inDraft) { return propertyNames => { @@ -107,17 +107,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors }; } - public static IFindFluent ContentSort(this IFindFluent cursor, Query query) + public static IFindFluent ContentSort(this IFindFluent cursor, ClrQuery query) { return cursor.Sort(query.BuildSort()); } - public static IFindFluent ContentTake(this IFindFluent cursor, Query query) + public static IFindFluent ContentTake(this IFindFluent cursor, ClrQuery query) { return cursor.Take(query); } - public static IFindFluent ContentSkip(this IFindFluent cursor, Query query) + public static IFindFluent ContentSkip(this IFindFluent cursor, ClrQuery query) { return cursor.Skip(query); } @@ -142,12 +142,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors return CreateFilter(null, schemaId, ids, status, null); } - public static FilterDefinition ToFilter(this Query query, Guid schemaId, ICollection ids, Status[] status) + public static FilterDefinition ToFilter(this ClrQuery query, Guid schemaId, ICollection ids, Status[] status) { return CreateFilter(null, schemaId, ids, status, query); } - private static FilterDefinition CreateFilter(Guid? appId, Guid? schemaId, ICollection ids, Status[] status, Query query) + private static FilterDefinition CreateFilter(Guid? appId, Guid? schemaId, ICollection ids, Status[] status, + ClrQuery query) { var filters = new List>(); @@ -188,7 +189,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors return Filter.And(filters); } - public static FilterDefinition ToFilter(this FilterNode filterNode, Guid schemaId) + public static FilterDefinition ToFilter(this FilterNode filterNode, Guid schemaId) { var filters = new List> { diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs index 0dc427439..7d1fe6cfd 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs @@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Assets return assets.SortSet(x => x.Id, ids); } - private Query ParseQuery(Context context, string query) + private ClrQuery ParseQuery(Context context, string query) { try { diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs b/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs index 17f201e0d..d17b620ca 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries { - public sealed class FilterTagTransformer : TransformVisitor + public sealed class FilterTagTransformer : TransformVisitor { private readonly ITagService tagService; private readonly Guid appId; @@ -25,22 +25,22 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries this.tagService = tagService; } - public static FilterNode Transform(FilterNode nodeIn, Guid appId, ITagService tagService) + public static FilterNode Transform(FilterNode nodeIn, Guid appId, ITagService tagService) { Guard.NotNull(tagService, nameof(tagService)); return nodeIn.Accept(new FilterTagTransformer(appId, tagService)); } - public override FilterNode Visit(FilterComparison nodeIn) + public override FilterNode Visit(CompareFilter nodeIn) { - if (string.Equals(nodeIn.Lhs[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Rhs.Value is string stringValue) + if (string.Equals(nodeIn.Path[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Value.Value is string stringValue) { var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, HashSet.Of(stringValue))).Result; if (tagNames.TryGetValue(stringValue, out var normalized)) { - return new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(normalized)); + return new CompareFilter(nodeIn.Path, nodeIn.Operator, normalized); } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs index 533ce993f..dde7fa42a 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs @@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories { Task> QueryByHashAsync(Guid appId, string hash); - Task> QueryAsync(Guid appId, Query query); + Task> QueryAsync(Guid appId, ClrQuery query); Task> QueryAsync(Guid appId, HashSet ids); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index 3ff9ae501..28d9b75e9 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Contents return await assetRepository.QueryAsync(appEntity.Id, new HashSet(assetIds)); } - private async Task> QueryContentsAsync(Guid filterSchemaId, FilterNode filterNode) + private async Task> QueryContentsAsync(Guid filterSchemaId, FilterNode filterNode) { return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 750d964a5..098998709 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -254,7 +254,7 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private Query ParseQuery(Context context, string query, ISchemaEntity schema) + private ClrQuery ParseQuery(Context context, string query, ISchemaEntity schema) { using (Profiler.TraceMethod()) { @@ -362,7 +362,7 @@ namespace Squidex.Domain.Apps.Entities.Contents return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet(ids), WithDraft(context)); } - private Task> QueryCoreAsync(Context context, ISchemaEntity schema, Query query) + private Task> QueryCoreAsync(Context context, ISchemaEntity schema, ClrQuery query) { return contentRepository.QueryAsync(context.App, schema, GetStatus(context), context.IsFrontendClient, query, WithDraft(context)); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs b/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs index 87d319651..ed2ca631a 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs @@ -16,7 +16,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.Contents.Queries { - public sealed class FilterTagTransformer : TransformVisitor + public sealed class FilterTagTransformer : TransformVisitor { private readonly ITagService tagService; private readonly ISchemaEntity schema; @@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries this.tagService = tagService; } - public static FilterNode Transform(FilterNode nodeIn, Guid appId, ISchemaEntity schema, ITagService tagService) + public static FilterNode Transform(FilterNode nodeIn, Guid appId, ISchemaEntity schema, ITagService tagService) { Guard.NotNull(tagService, nameof(tagService)); Guard.NotNull(schema, nameof(schema)); @@ -37,15 +37,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries return nodeIn.Accept(new FilterTagTransformer(appId, schema, tagService)); } - public override FilterNode Visit(FilterComparison nodeIn) + public override FilterNode Visit(CompareFilter nodeIn) { - if (nodeIn.Rhs.Value is string stringValue && IsDataPath(nodeIn.Lhs) && IsTagField(nodeIn.Lhs)) + if (nodeIn.Value.Value is string stringValue && IsDataPath(nodeIn.Path) && IsTagField(nodeIn.Path)) { var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Schemas(schema.Id), HashSet.Of(stringValue))).Result; if (tagNames.TryGetValue(stringValue, out var normalized)) { - return new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(normalized)); + return new CompareFilter(nodeIn.Path, nodeIn.Operator, normalized); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index bb8f44188..ca183aad1 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -23,9 +23,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids, bool includeDraft); - Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, Query query, bool includeDraft); + Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, ClrQuery query, bool includeDraft); - Task> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode); + Task> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode); Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id, bool includeDraft); diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs index 3b76605e0..8743af7c2 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs @@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries { public static class FilterBuilder { - public static (FilterDefinition Filter, bool Last) BuildFilter(this Query query, bool supportsSearch = true) + public static (FilterDefinition Filter, bool Last) BuildFilter(this ClrQuery query, bool supportsSearch = true) { if (query.FullText != null) { @@ -32,7 +32,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries return (null, false); } - public static FilterDefinition BuildFilter(this FilterNode filterNode) + public static FilterDefinition BuildFilter(this FilterNode filterNode) { return FilterVisitor.Visit(filterNode); } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs index 54e60ec29..60bbbf4d6 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs @@ -14,7 +14,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Infrastructure.MongoDb.Queries { - public sealed class FilterVisitor : FilterNodeVisitor> + public sealed class FilterVisitor : FilterNodeVisitor, ClrValue> { private static readonly FilterDefinitionBuilder Filter = Builders.Filter; private static readonly FilterVisitor Instance = new FilterVisitor(); @@ -23,64 +23,64 @@ namespace Squidex.Infrastructure.MongoDb.Queries { } - public static FilterDefinition Visit(FilterNode node) + public static FilterDefinition Visit(FilterNode node) { return node.Accept(Instance); } - public override FilterDefinition Visit(FilterNegate nodeIn) + public override FilterDefinition Visit(NegateFilter nodeIn) { - return Filter.Not(nodeIn.Operand.Accept(this)); + return Filter.Not(nodeIn.Filter.Accept(this)); } - public override FilterDefinition Visit(FilterJunction nodeIn) + public override FilterDefinition Visit(LogicalFilter nodeIn) { - if (nodeIn.JunctionType == FilterJunctionType.And) + if (nodeIn.Type == LogicalFilterType.And) { - return Filter.And(nodeIn.Operands.Select(x => x.Accept(this))); + return Filter.And(nodeIn.Filters.Select(x => x.Accept(this))); } else { - return Filter.Or(nodeIn.Operands.Select(x => x.Accept(this))); + return Filter.Or(nodeIn.Filters.Select(x => x.Accept(this))); } } - public override FilterDefinition Visit(FilterComparison nodeIn) + public override FilterDefinition Visit(CompareFilter nodeIn) { - var propertyName = string.Join(".", nodeIn.Lhs); + var propertyName = nodeIn.Path.ToString(); switch (nodeIn.Operator) { - case FilterOperator.Empty: + case CompareOperator.Empty: return Filter.Or(Filter.Exists(propertyName, false), Filter.Eq(propertyName, default(T)), Filter.Eq(propertyName, string.Empty), Filter.Eq(propertyName, new T[0])); - case FilterOperator.StartsWith: + case CompareOperator.StartsWith: return Filter.Regex(propertyName, BuildRegex(nodeIn, s => "^" + s)); - case FilterOperator.Contains: + case CompareOperator.Contains: return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s)); - case FilterOperator.EndsWith: + case CompareOperator.EndsWith: return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s + "$")); - case FilterOperator.Equals: - return Filter.Eq(propertyName, nodeIn.Rhs.Value); - case FilterOperator.GreaterThan: - return Filter.Gt(propertyName, nodeIn.Rhs.Value); - case FilterOperator.GreaterThanOrEqual: - return Filter.Gte(propertyName, nodeIn.Rhs.Value); - case FilterOperator.LessThan: - return Filter.Lt(propertyName, nodeIn.Rhs.Value); - case FilterOperator.LessThanOrEqual: - return Filter.Lte(propertyName, nodeIn.Rhs.Value); - case FilterOperator.NotEquals: - return Filter.Ne(propertyName, nodeIn.Rhs.Value); - case FilterOperator.In: - return Filter.In(propertyName, ((IList)nodeIn.Rhs.Value).OfType()); + case CompareOperator.Equals: + return Filter.Eq(propertyName, nodeIn.Value.Value); + case CompareOperator.GreaterThan: + return Filter.Gt(propertyName, nodeIn.Value.Value); + case CompareOperator.GreaterThanOrEqual: + return Filter.Gte(propertyName, nodeIn.Value.Value); + case CompareOperator.LessThan: + return Filter.Lt(propertyName, nodeIn.Value.Value); + case CompareOperator.LessThanOrEqual: + return Filter.Lte(propertyName, nodeIn.Value.Value); + case CompareOperator.NotEquals: + return Filter.Ne(propertyName, nodeIn.Value.Value); + case CompareOperator.In: + return Filter.In(propertyName, ((IList)nodeIn.Value.Value).OfType()); } throw new NotSupportedException(); } - private static BsonRegularExpression BuildRegex(FilterComparison node, Func formatter) + private static BsonRegularExpression BuildRegex(CompareFilter node, Func formatter) { - return new BsonRegularExpression(formatter(node.Rhs.Value.ToString()), "i"); + return new BsonRegularExpression(formatter(node.Value.Value.ToString()), "i"); } } } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs index 442aaab2e..977641387 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs @@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries { public static class LimitExtensions { - public static IFindFluent Take(this IFindFluent cursor, Query query) + public static IFindFluent Take(this IFindFluent cursor, ClrQuery query) { if (query.Take < long.MaxValue) { @@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries return cursor; } - public static IFindFluent Skip(this IFindFluent cursor, Query query) + public static IFindFluent Skip(this IFindFluent cursor, ClrQuery query) { if (query.Skip > 0) { diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs index caab0a4c3..717b2299f 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs @@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries { public static class SortBuilder { - public static SortDefinition BuildSort(this Query query) + public static SortDefinition BuildSort(this ClrQuery query) { if (query.Sort.Count > 0) { diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs index 03acfc2f5..5bce0855f 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs @@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft { public abstract class JsonClassConverter : JsonConverter, ISupportedTypes where T : class { - public IEnumerable SupportedTypes + public virtual IEnumerable SupportedTypes { get { yield return typeof(T); } } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs index 11d45a037..dacf54841 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs @@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft protected override Language ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - if (reader.TokenType != JsonToken.String) - { - throw new JsonException($"Expected String, but got {reader.TokenType}."); - } + var value = serializer.Deserialize(reader); - return Language.GetLanguage(reader.Value.ToString()); + return Language.GetLanguage(value); } } } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs index f1b141060..f5f753d79 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs @@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft protected override NamedId ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - if (reader.TokenType != JsonToken.String) - { - throw new JsonException($"Expected String, but got {reader.TokenType}."); - } + var value = serializer.Deserialize(reader); - if (!NamedId.TryParse(reader.Value.ToString(), Guid.TryParse, out var result)) + if (!NamedId.TryParse(value, Guid.TryParse, out var result)) { throw new JsonException("Named id must have more than 2 parts divided by commata."); } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs index d984e572b..494bce251 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs @@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft protected override NamedId ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - if (reader.TokenType != JsonToken.String) - { - throw new JsonException($"Expected String, but got {reader.TokenType}."); - } + var value = serializer.Deserialize(reader); - if (!NamedId.TryParse(reader.Value.ToString(), long.TryParse, out var result)) + if (!NamedId.TryParse(value, long.TryParse, out var result)) { throw new JsonException("Named id must have at least 2 parts divided by commata."); } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs index 573cbaa57..8eba29cfd 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs @@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft protected override NamedId ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - if (reader.TokenType != JsonToken.String) - { - throw new JsonException($"Expected String, but got {reader.TokenType}."); - } + var value = serializer.Deserialize(reader); - if (!NamedId.TryParse(reader.Value.ToString(), ParseString, out var result)) + if (!NamedId.TryParse(value, ParseString, out var result)) { throw new JsonException("Named id must have at least 2 parts divided by commata."); } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs index 988f3ff95..3017fce97 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs @@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft protected override RefToken ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - if (reader.TokenType != JsonToken.String) - { - throw new JsonException($"Expected String, but got {reader.TokenType}."); - } + var value = serializer.Deserialize(reader); - if (!RefToken.TryParse(reader.Value.ToString(), out var result)) + if (!RefToken.TryParse(value, out var result)) { throw new JsonException("Named id must have at least 2 parts divided by colon."); } diff --git a/src/Squidex.Infrastructure/Queries/ClrFilter.cs b/src/Squidex.Infrastructure/Queries/ClrFilter.cs new file mode 100644 index 000000000..c2cb7eaa4 --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/ClrFilter.cs @@ -0,0 +1,87 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Queries +{ + public static class ClrFilter + { + public static LogicalFilter And(params FilterNode[] filters) + { + return new LogicalFilter(LogicalFilterType.And, filters); + } + + public static LogicalFilter Or(params FilterNode[] filters) + { + return new LogicalFilter(LogicalFilterType.Or, filters); + } + + public static NegateFilter Not(FilterNode filter) + { + return new NegateFilter(filter); + } + + public static CompareFilter Eq(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.Equals, value); + } + + public static CompareFilter Ne(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.NotEquals, value); + } + + public static CompareFilter Lt(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.LessThan, value); + } + + public static CompareFilter Le(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.LessThanOrEqual, value); + } + + public static CompareFilter Gt(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.GreaterThan, value); + } + + public static CompareFilter Ge(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.GreaterThanOrEqual, value); + } + + public static CompareFilter Contains(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.Contains, value); + } + + public static CompareFilter EndsWith(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.EndsWith, value); + } + + public static CompareFilter StartsWith(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.StartsWith, value); + } + + public static CompareFilter Empty(PropertyPath path) + { + return Binary(path, CompareOperator.Empty, null); + } + + public static CompareFilter In(PropertyPath path, ClrValue value) + { + return Binary(path, CompareOperator.In, value); + } + + private static CompareFilter Binary(PropertyPath path, CompareOperator @operator, ClrValue value) + { + return new CompareFilter(path, @operator, value ?? ClrValue.Null); + } + } +} diff --git a/src/Squidex.Infrastructure/Queries/ClrQuery.cs b/src/Squidex.Infrastructure/Queries/ClrQuery.cs new file mode 100644 index 000000000..0d94e18fe --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/ClrQuery.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Queries +{ + public sealed class ClrQuery : Query + { + } +} diff --git a/src/Squidex.Infrastructure/Queries/ClrValue.cs b/src/Squidex.Infrastructure/Queries/ClrValue.cs new file mode 100644 index 000000000..1a0719486 --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/ClrValue.cs @@ -0,0 +1,140 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using NodaTime; + +namespace Squidex.Infrastructure.Queries +{ + public sealed class ClrValue + { + public static readonly ClrValue Null = new ClrValue(null, ClrValueType.Null, false); + + public object Value { get; } + + public ClrValueType ValueType { get; } + + public bool IsList { get; } + + private ClrValue(object value, ClrValueType valueType, bool isList) + { + Value = value; + ValueType = valueType; + + IsList = isList; + } + + public static implicit operator ClrValue(Instant value) + { + return new ClrValue(value, ClrValueType.Instant, false); + } + + public static implicit operator ClrValue(Guid value) + { + return new ClrValue(value, ClrValueType.Guid, false); + } + + public static implicit operator ClrValue(bool value) + { + return new ClrValue(value, ClrValueType.Boolean, false); + } + + public static implicit operator ClrValue(float value) + { + return new ClrValue(value, ClrValueType.Single, false); + } + + public static implicit operator ClrValue(double value) + { + return new ClrValue(value, ClrValueType.Double, false); + } + + public static implicit operator ClrValue(int value) + { + return new ClrValue(value, ClrValueType.Int32, false); + } + + public static implicit operator ClrValue(long value) + { + return new ClrValue(value, ClrValueType.Int64, false); + } + + public static implicit operator ClrValue(string value) + { + return value != null ? new ClrValue(value, ClrValueType.String, false) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Instant, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Guid, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Boolean, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Single, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Double, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Int32, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.Int64, true) : Null; + } + + public static implicit operator ClrValue(List value) + { + return value != null ? new ClrValue(value, ClrValueType.String, true) : Null; + } + + public override string ToString() + { + if (Value is IList list) + { + return $"[{string.Join(", ", list.OfType().Select(ToString).ToArray())}]"; + } + + return ToString(Value); + } + + private static string ToString(object value) + { + if (value == null) + { + return "null"; + } + + if (value is string s) + { + return $"'{s.Replace("'", "\\'")}'"; + } + + return string.Format(CultureInfo.InvariantCulture, "{0}", value); + } + } +} diff --git a/src/Squidex.Infrastructure/Queries/FilterValueType.cs b/src/Squidex.Infrastructure/Queries/ClrValueType.cs similarity index 94% rename from src/Squidex.Infrastructure/Queries/FilterValueType.cs rename to src/Squidex.Infrastructure/Queries/ClrValueType.cs index cdb64a139..338c9b8a3 100644 --- a/src/Squidex.Infrastructure/Queries/FilterValueType.cs +++ b/src/Squidex.Infrastructure/Queries/ClrValueType.cs @@ -7,7 +7,7 @@ namespace Squidex.Infrastructure.Queries { - public enum FilterValueType + public enum ClrValueType { Boolean, Guid, diff --git a/src/Squidex.Infrastructure/Queries/CompareFilter.cs b/src/Squidex.Infrastructure/Queries/CompareFilter.cs new file mode 100644 index 000000000..5b522e6c9 --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/CompareFilter.cs @@ -0,0 +1,67 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Queries +{ + public sealed class CompareFilter : FilterNode + { + public PropertyPath Path { get; } + + public CompareOperator Operator { get; } + + public TValue Value { get; } + + public CompareFilter(PropertyPath path, CompareOperator @operator, TValue value) + { + Guard.NotNull(path, nameof(path)); + Guard.NotNull(value, nameof(value)); + Guard.Enum(@operator, nameof(@operator)); + + Path = path; + + Operator = @operator; + + Value = value; + } + + public override T Accept(FilterNodeVisitor visitor) + { + return visitor.Visit(this); + } + + public override string ToString() + { + switch (Operator) + { + case CompareOperator.Contains: + return $"contains({Path}, {Value})"; + case CompareOperator.Empty: + return $"empty({Path})"; + case CompareOperator.EndsWith: + return $"endsWith({Path}, {Value})"; + case CompareOperator.StartsWith: + return $"startsWith({Path}, {Value})"; + case CompareOperator.Equals: + return $"{Path} == {Value}"; + case CompareOperator.NotEquals: + return $"{Path} != {Value}"; + case CompareOperator.GreaterThan: + return $"{Path} > {Value}"; + case CompareOperator.GreaterThanOrEqual: + return $"{Path} >= {Value}"; + case CompareOperator.LessThan: + return $"{Path} < {Value}"; + case CompareOperator.LessThanOrEqual: + return $"{Path} <= {Value}"; + case CompareOperator.In: + return $"{Path} in {Value}"; + default: + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Queries/FilterOperator.cs b/src/Squidex.Infrastructure/Queries/CompareOperator.cs similarity index 95% rename from src/Squidex.Infrastructure/Queries/FilterOperator.cs rename to src/Squidex.Infrastructure/Queries/CompareOperator.cs index 5496584ea..98f65edb7 100644 --- a/src/Squidex.Infrastructure/Queries/FilterOperator.cs +++ b/src/Squidex.Infrastructure/Queries/CompareOperator.cs @@ -7,7 +7,7 @@ namespace Squidex.Infrastructure.Queries { - public enum FilterOperator + public enum CompareOperator { Contains, Empty, diff --git a/src/Squidex.Infrastructure/Queries/FilterBuilder.cs b/src/Squidex.Infrastructure/Queries/FilterBuilder.cs deleted file mode 100644 index 389d2c4e1..000000000 --- a/src/Squidex.Infrastructure/Queries/FilterBuilder.cs +++ /dev/null @@ -1,85 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Linq; -using NodaTime; - -namespace Squidex.Infrastructure.Queries -{ - public static class FilterBuilder - { - public static FilterJunction And(params FilterNode[] operands) - { - return new FilterJunction(FilterJunctionType.And, operands); - } - - public static FilterJunction Or(params FilterNode[] operands) - { - return new FilterJunction(FilterJunctionType.Or, operands); - } - - public static FilterComparison Eq(string path, string value) - { - return Binary(path, FilterOperator.Equals, value); - } - - public static FilterComparison Eq(string path, bool value) - { - return Binary(path, FilterOperator.Equals, value); - } - - public static FilterComparison Eq(string path, long value) - { - return Binary(path, FilterOperator.Equals, value); - } - - public static FilterComparison Eq(string path, int value) - { - return Binary(path, FilterOperator.Equals, value); - } - - public static FilterComparison Eq(string path, Instant value) - { - return Binary(path, FilterOperator.Equals, value); - } - - public static FilterComparison Empty(string path) - { - return new FilterComparison(path.Split('.', '/'), FilterOperator.Empty, FilterValue.Null); - } - - public static FilterComparison In(string path, params long[] value) - { - return new FilterComparison(path.Split('.', '/'), FilterOperator.In, new FilterValue(value.ToList())); - } - - private static FilterComparison Binary(string path, FilterOperator @operator, string value) - { - return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value)); - } - - private static FilterComparison Binary(string path, FilterOperator @operator, bool value) - { - return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value)); - } - - private static FilterComparison Binary(string path, FilterOperator @operator, long value) - { - return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value)); - } - - private static FilterComparison Binary(string path, FilterOperator @operator, int value) - { - return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value)); - } - - private static FilterComparison Binary(string path, FilterOperator @operator, Instant value) - { - return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value)); - } - } -} diff --git a/src/Squidex.Infrastructure/Queries/FilterComparison.cs b/src/Squidex.Infrastructure/Queries/FilterComparison.cs deleted file mode 100644 index 9f19a5dcd..000000000 --- a/src/Squidex.Infrastructure/Queries/FilterComparison.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; - -namespace Squidex.Infrastructure.Queries -{ - public sealed class FilterComparison : FilterNode - { - public IReadOnlyList Lhs { get; } - - public FilterOperator Operator { get; } - - public FilterValue Rhs { get; } - - public FilterComparison(IReadOnlyList lhs, FilterOperator @operator, FilterValue rhs) - { - Guard.NotNull(lhs, nameof(lhs)); - Guard.NotEmpty(lhs, nameof(lhs)); - Guard.Enum(@operator, nameof(@operator)); - - Lhs = lhs; - Rhs = rhs; - - Operator = @operator; - } - - public override T Accept(FilterNodeVisitor visitor) - { - return visitor.Visit(this); - } - - public override string ToString() - { - var path = string.Join(".", Lhs); - - switch (Operator) - { - case FilterOperator.Contains: - return $"contains({path}, {Rhs})"; - case FilterOperator.Empty: - return $"empty({path})"; - case FilterOperator.EndsWith: - return $"endsWith({path}, {Rhs})"; - case FilterOperator.StartsWith: - return $"startsWith({path}, {Rhs})"; - case FilterOperator.Equals: - return $"{path} == {Rhs}"; - case FilterOperator.NotEquals: - return $"{path} != {Rhs}"; - case FilterOperator.GreaterThan: - return $"{path} > {Rhs}"; - case FilterOperator.GreaterThanOrEqual: - return $"{path} >= {Rhs}"; - case FilterOperator.LessThan: - return $"{path} < {Rhs}"; - case FilterOperator.LessThanOrEqual: - return $"{path} <= {Rhs}"; - case FilterOperator.In: - return $"{path} in {Rhs}"; - default: - return string.Empty; - } - } - } -} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Queries/FilterJunction.cs b/src/Squidex.Infrastructure/Queries/FilterJunction.cs deleted file mode 100644 index 08c2bf2a9..000000000 --- a/src/Squidex.Infrastructure/Queries/FilterJunction.cs +++ /dev/null @@ -1,45 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using System.Linq; - -namespace Squidex.Infrastructure.Queries -{ - public sealed class FilterJunction : FilterNode - { - public IReadOnlyList Operands { get; } - - public FilterJunctionType JunctionType { get; } - - public FilterJunction(FilterJunctionType junctionType, IReadOnlyList operands) - { - Guard.NotNull(operands, nameof(operands)); - Guard.GreaterEquals(operands.Count, 2, nameof(operands.Count)); - Guard.Enum(junctionType, nameof(junctionType)); - - Operands = operands; - - JunctionType = junctionType; - } - - public FilterJunction(FilterJunctionType junctionType, params FilterNode[] operands) - : this(junctionType, operands?.ToList()) - { - } - - public override T Accept(FilterNodeVisitor visitor) - { - return visitor.Visit(this); - } - - public override string ToString() - { - return $"({string.Join(JunctionType == FilterJunctionType.And ? " && " : " || ", Operands)})"; - } - } -} diff --git a/src/Squidex.Infrastructure/Queries/FilterNode.cs b/src/Squidex.Infrastructure/Queries/FilterNode.cs index 61f348538..8dd348ce7 100644 --- a/src/Squidex.Infrastructure/Queries/FilterNode.cs +++ b/src/Squidex.Infrastructure/Queries/FilterNode.cs @@ -7,8 +7,8 @@ namespace Squidex.Infrastructure.Queries { - public abstract class FilterNode + public abstract class FilterNode { - public abstract T Accept(FilterNodeVisitor visitor); + public abstract T Accept(FilterNodeVisitor visitor); } } diff --git a/src/Squidex.Infrastructure/Queries/FilterNodeVisitor.cs b/src/Squidex.Infrastructure/Queries/FilterNodeVisitor.cs index 30ef01741..a18f9ea62 100644 --- a/src/Squidex.Infrastructure/Queries/FilterNodeVisitor.cs +++ b/src/Squidex.Infrastructure/Queries/FilterNodeVisitor.cs @@ -11,19 +11,19 @@ using System; namespace Squidex.Infrastructure.Queries { - public abstract class FilterNodeVisitor + public abstract class FilterNodeVisitor { - public virtual T Visit(FilterComparison nodeIn) + public virtual T Visit(CompareFilter nodeIn) { throw new NotImplementedException(); } - public virtual T Visit(FilterJunction nodeIn) + public virtual T Visit(LogicalFilter nodeIn) { throw new NotImplementedException(); } - public virtual T Visit(FilterNegate nodeIn) + public virtual T Visit(NegateFilter nodeIn) { throw new NotImplementedException(); } diff --git a/src/Squidex.Infrastructure/Queries/FilterValue.cs b/src/Squidex.Infrastructure/Queries/FilterValue.cs deleted file mode 100644 index 88a1746d0..000000000 --- a/src/Squidex.Infrastructure/Queries/FilterValue.cs +++ /dev/null @@ -1,149 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using NodaTime; - -namespace Squidex.Infrastructure.Queries -{ - public sealed class FilterValue - { - public static readonly FilterValue Null = new FilterValue(null, FilterValueType.Null, false); - - public object Value { get; } - - public FilterValueType ValueType { get; } - - public bool IsList { get; } - - public FilterValue(Guid value) - : this(value, FilterValueType.Guid, false) - { - } - - public FilterValue(Instant value) - : this(value, FilterValueType.Instant, false) - { - } - - public FilterValue(bool value) - : this(value, FilterValueType.Boolean, false) - { - } - - public FilterValue(float value) - : this(value, FilterValueType.Single, false) - { - } - - public FilterValue(double value) - : this(value, FilterValueType.Double, false) - { - } - - public FilterValue(int value) - : this(value, FilterValueType.Int32, false) - { - } - - public FilterValue(long value) - : this(value, FilterValueType.Int64, false) - { - } - - public FilterValue(string value) - : this(value, FilterValueType.String, false) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Guid, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Instant, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Boolean, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Single, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Double, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Int32, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.Int64, true) - { - Guard.NotNull(value, nameof(value)); - } - - public FilterValue(List value) - : this(value, FilterValueType.String, true) - { - Guard.NotNull(value, nameof(value)); - } - - private FilterValue(object value, FilterValueType valueType, bool isList) - { - Value = value; - ValueType = valueType; - - IsList = isList; - } - - public override string ToString() - { - if (Value is IList list) - { - return $"[{string.Join(", ", list.OfType().Select(ToString).ToArray())}]"; - } - - return ToString(Value); - } - - private static string ToString(object value) - { - if (value == null) - { - return "null"; - } - - if (value is string s) - { - return $"'{s.Replace("'", "\\'")}'"; - } - - return string.Format(CultureInfo.InvariantCulture, "{0}", value); - } - } -} diff --git a/src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs b/src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs new file mode 100644 index 000000000..76a1ce0af --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs @@ -0,0 +1,162 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Squidex.Infrastructure.Json.Newtonsoft; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Infrastructure.Queries.Json +{ + public sealed class FilterConverter : JsonClassConverter> + { + public override IEnumerable SupportedTypes + { + get + { + yield return typeof(CompareFilter); + yield return typeof(FilterNode); + yield return typeof(LogicalFilter); + yield return typeof(NegateFilter); + } + } + + public override bool CanConvert(Type objectType) + { + return SupportedTypes.Contains(objectType); + } + + protected override FilterNode ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) + { + throw new JsonException($"Expected StartObject, but got {reader.TokenType}."); + } + + FilterNode result = null; + + var comparePath = (PropertyPath)null; + var compareOperator = (CompareOperator)99; + var compareValue = (IJsonValue)null; + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string propertyName = reader.Value.ToString(); + + if (!reader.Read()) + { + throw new JsonSerializationException("Unexpected end when reading filter."); + } + + if (result != null) + { + throw new JsonSerializationException($"Unexpected property {propertyName}"); + } + + switch (propertyName.ToLowerInvariant()) + { + case "not": + var filter = serializer.Deserialize>(reader); + + result = new NegateFilter(filter); + break; + case "and": + var andFilters = serializer.Deserialize>>(reader); + + result = new LogicalFilter(LogicalFilterType.And, andFilters); + break; + case "or": + var orFilters = serializer.Deserialize>>(reader); + + result = new LogicalFilter(LogicalFilterType.Or, orFilters); + break; + case "path": + comparePath = serializer.Deserialize(reader); + break; + case "op": + compareOperator = ReadOperator(reader, serializer); + break; + case "value": + compareValue = serializer.Deserialize(reader); + break; + } + + break; + case JsonToken.Comment: + break; + case JsonToken.EndObject: + if (result != null) + { + return result; + } + + if (comparePath == null) + { + throw new JsonSerializationException("Path not defined."); + } + + if (compareValue == null && compareOperator != CompareOperator.Empty) + { + throw new JsonSerializationException("Value not defined."); + } + + if (!compareOperator.IsEnumValue()) + { + throw new JsonSerializationException("Operator not defined."); + } + + return new CompareFilter(comparePath, compareOperator, compareValue ?? JsonValue.Null); + } + } + + throw new JsonSerializationException("Unexpected end when reading filter."); + } + + private static CompareOperator ReadOperator(JsonReader reader, JsonSerializer serializer) + { + var value = serializer.Deserialize(reader); + + switch (value.ToLowerInvariant()) + { + case "eq": + return CompareOperator.Equals; + case "ne": + return CompareOperator.NotEquals; + case "lt": + return CompareOperator.LessThan; + case "le": + return CompareOperator.LessThanOrEqual; + case "gt": + return CompareOperator.GreaterThan; + case "ge": + return CompareOperator.GreaterThanOrEqual; + case "empty": + return CompareOperator.Empty; + case "contains": + return CompareOperator.Contains; + case "endswith": + return CompareOperator.EndsWith; + case "startswith": + return CompareOperator.StartsWith; + case "in": + return CompareOperator.In; + } + + throw new JsonSerializationException($"Unexpected compare operator, got {value}."); + } + + protected override void WriteValue(JsonWriter writer, FilterNode value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs b/src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs new file mode 100644 index 000000000..15994b713 --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Squidex.Infrastructure.Json.Newtonsoft; + +namespace Squidex.Infrastructure.Queries.Json +{ + public sealed class PropertyPathConverter : JsonClassConverter + { + protected override PropertyPath ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) + { + var value = serializer.Deserialize(reader); + + return value; + } + + protected override void WriteValue(JsonWriter writer, PropertyPath value, JsonSerializer serializer) + { + serializer.Serialize(writer, (IEnumerable)value); + } + } +} diff --git a/src/Squidex.Infrastructure/Queries/LogicalFilter.cs b/src/Squidex.Infrastructure/Queries/LogicalFilter.cs new file mode 100644 index 000000000..27456177f --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/LogicalFilter.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; + +namespace Squidex.Infrastructure.Queries +{ + public sealed class LogicalFilter : FilterNode + { + public IReadOnlyList> Filters { get; } + + public LogicalFilterType Type { get; } + + public LogicalFilter(LogicalFilterType type, IReadOnlyList> filters) + { + Guard.NotNull(filters, nameof(filters)); + Guard.GreaterEquals(filters.Count, 2, nameof(filters.Count)); + Guard.Enum(type, nameof(type)); + + Filters = filters; + + Type = type; + } + + public override T Accept(FilterNodeVisitor visitor) + { + return visitor.Visit(this); + } + + public override string ToString() + { + return $"({string.Join(Type == LogicalFilterType.And ? " && " : " || ", Filters)})"; + } + } +} diff --git a/src/Squidex.Infrastructure/Queries/FilterJunctionType.cs b/src/Squidex.Infrastructure/Queries/LogicalFilterType.cs similarity index 92% rename from src/Squidex.Infrastructure/Queries/FilterJunctionType.cs rename to src/Squidex.Infrastructure/Queries/LogicalFilterType.cs index c9c8b8289..4d34eb1ea 100644 --- a/src/Squidex.Infrastructure/Queries/FilterJunctionType.cs +++ b/src/Squidex.Infrastructure/Queries/LogicalFilterType.cs @@ -7,7 +7,7 @@ namespace Squidex.Infrastructure.Queries { - public enum FilterJunctionType + public enum LogicalFilterType { And, Or diff --git a/src/Squidex.Infrastructure/Queries/FilterNegate.cs b/src/Squidex.Infrastructure/Queries/NegateFilter.cs similarity index 61% rename from src/Squidex.Infrastructure/Queries/FilterNegate.cs rename to src/Squidex.Infrastructure/Queries/NegateFilter.cs index 650170444..09583a0f8 100644 --- a/src/Squidex.Infrastructure/Queries/FilterNegate.cs +++ b/src/Squidex.Infrastructure/Queries/NegateFilter.cs @@ -7,25 +7,25 @@ namespace Squidex.Infrastructure.Queries { - public sealed class FilterNegate : FilterNode + public sealed class NegateFilter : FilterNode { - public FilterNode Operand { get; } + public FilterNode Filter { get; } - public FilterNegate(FilterNode operand) + public NegateFilter(FilterNode filter) { - Guard.NotNull(operand, nameof(operand)); + Guard.NotNull(filter, nameof(filter)); - Operand = operand; + Filter = filter; } - public override T Accept(FilterNodeVisitor visitor) + public override T Accept(FilterNodeVisitor visitor) { return visitor.Visit(this); } public override string ToString() { - return $"!({Operand})"; + return $"!({Filter})"; } } } diff --git a/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs b/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs index df455a1b0..db3fb47b0 100644 --- a/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs +++ b/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs @@ -15,7 +15,7 @@ using NodaTime.Text; namespace Squidex.Infrastructure.Queries.OData { - public sealed class ConstantWithTypeVisitor : QueryNodeVisitor + public sealed class ConstantWithTypeVisitor : QueryNodeVisitor { private static readonly IEdmPrimitiveType BooleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean); private static readonly IEdmPrimitiveType DateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset); @@ -32,117 +32,117 @@ namespace Squidex.Infrastructure.Queries.OData { } - public static FilterValue Visit(QueryNode node) + public static ClrValue Visit(QueryNode node) { return node.Accept(Instance); } - public override FilterValue Visit(ConvertNode nodeIn) + public override ClrValue Visit(ConvertNode nodeIn) { if (nodeIn.TypeReference.Definition == BooleanType) { var value = ConstantVisitor.Visit(nodeIn.Source); - return new FilterValue(bool.Parse(value.ToString())); + return bool.Parse(value.ToString()); } if (nodeIn.TypeReference.Definition == GuidType) { var value = ConstantVisitor.Visit(nodeIn.Source); - return new FilterValue(Guid.Parse(value.ToString())); + return Guid.Parse(value.ToString()); } if (nodeIn.TypeReference.Definition == DateTimeType) { var value = ConstantVisitor.Visit(nodeIn.Source); - return new FilterValue(ParseInstant(value)); + return ParseInstant(value); } if (ConstantVisitor.Visit(nodeIn.Source) == null) { - return FilterValue.Null; + return ClrValue.Null; } throw new NotSupportedException(); } - public override FilterValue Visit(CollectionConstantNode nodeIn) + public override ClrValue Visit(CollectionConstantNode nodeIn) { if (nodeIn.ItemType.Definition == DateTimeType) { - return new FilterValue(nodeIn.Collection.Select(x => ParseInstant(x.Value)).ToList()); + return nodeIn.Collection.Select(x => ParseInstant(x.Value)).ToList(); } if (nodeIn.ItemType.Definition == GuidType) { - return new FilterValue(nodeIn.Collection.Select(x => (Guid)x.Value).ToList()); + return nodeIn.Collection.Select(x => (Guid)x.Value).ToList(); } if (nodeIn.ItemType.Definition == BooleanType) { - return new FilterValue(nodeIn.Collection.Select(x => (bool)x.Value).ToList()); + return nodeIn.Collection.Select(x => (bool)x.Value).ToList(); } if (nodeIn.ItemType.Definition == SingleType) { - return new FilterValue(nodeIn.Collection.Select(x => (float)x.Value).ToList()); + return nodeIn.Collection.Select(x => (float)x.Value).ToList(); } if (nodeIn.ItemType.Definition == DoubleType) { - return new FilterValue(nodeIn.Collection.Select(x => (double)x.Value).ToList()); + return nodeIn.Collection.Select(x => (double)x.Value).ToList(); } if (nodeIn.ItemType.Definition == Int32Type) { - return new FilterValue(nodeIn.Collection.Select(x => (int)x.Value).ToList()); + return nodeIn.Collection.Select(x => (int)x.Value).ToList(); } if (nodeIn.ItemType.Definition == Int64Type) { - return new FilterValue(nodeIn.Collection.Select(x => (long)x.Value).ToList()); + return nodeIn.Collection.Select(x => (long)x.Value).ToList(); } if (nodeIn.ItemType.Definition == StringType) { - return new FilterValue(nodeIn.Collection.Select(x => (string)x.Value).ToList()); + return nodeIn.Collection.Select(x => (string)x.Value).ToList(); } throw new NotSupportedException(); } - public override FilterValue Visit(ConstantNode nodeIn) + public override ClrValue Visit(ConstantNode nodeIn) { if (nodeIn.TypeReference.Definition == BooleanType) { - return new FilterValue((bool)nodeIn.Value); + return (bool)nodeIn.Value; } if (nodeIn.TypeReference.Definition == SingleType) { - return new FilterValue((float)nodeIn.Value); + return (float)nodeIn.Value; } if (nodeIn.TypeReference.Definition == DoubleType) { - return new FilterValue((double)nodeIn.Value); + return (double)nodeIn.Value; } if (nodeIn.TypeReference.Definition == Int32Type) { - return new FilterValue((int)nodeIn.Value); + return (int)nodeIn.Value; } if (nodeIn.TypeReference.Definition == Int64Type) { - return new FilterValue((long)nodeIn.Value); + return (long)nodeIn.Value; } if (nodeIn.TypeReference.Definition == StringType) { - return new FilterValue((string)nodeIn.Value); + return (string)nodeIn.Value; } throw new NotSupportedException(); diff --git a/src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs b/src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs index e59535b38..47a5d0ed0 100644 --- a/src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs +++ b/src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs @@ -43,9 +43,9 @@ namespace Squidex.Infrastructure.Queries.OData return parser; } - public static Query ToQuery(this ODataUriParser parser) + public static ClrQuery ToQuery(this ODataUriParser parser) { - var query = new Query(); + var query = new ClrQuery(); if (parser != null) { diff --git a/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs b/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs index 6e378bb80..237a0141c 100644 --- a/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs +++ b/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs @@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.Queries.OData { public static class FilterBuilder { - public static void ParseFilter(this ODataUriParser query, Query result) + public static void ParseFilter(this ODataUriParser query, ClrQuery result) { SearchClause search; try diff --git a/src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs b/src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs index 15d4f2350..9d366ce54 100644 --- a/src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs +++ b/src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs @@ -11,7 +11,7 @@ using Microsoft.OData.UriParser; namespace Squidex.Infrastructure.Queries.OData { - public sealed class FilterVisitor : QueryNodeVisitor + public sealed class FilterVisitor : QueryNodeVisitor> { private static readonly FilterVisitor Instance = new FilterVisitor(); @@ -19,40 +19,40 @@ namespace Squidex.Infrastructure.Queries.OData { } - public static FilterNode Visit(QueryNode node) + public static FilterNode Visit(QueryNode node) { return node.Accept(Instance); } - public override FilterNode Visit(ConvertNode nodeIn) + public override FilterNode Visit(ConvertNode nodeIn) { return nodeIn.Source.Accept(this); } - public override FilterNode Visit(UnaryOperatorNode nodeIn) + public override FilterNode Visit(UnaryOperatorNode nodeIn) { if (nodeIn.OperatorKind == UnaryOperatorKind.Not) { - return new FilterNegate(nodeIn.Operand.Accept(this)); + return ClrFilter.Not(nodeIn.Operand.Accept(this)); } throw new NotSupportedException(); } - public override FilterNode Visit(InNode nodeIn) + public override FilterNode Visit(InNode nodeIn) { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.In, value); + return ClrFilter.In(PropertyPathVisitor.Visit(nodeIn.Left), value); } - public override FilterNode Visit(SingleValueFunctionCallNode nodeIn) + public override FilterNode Visit(SingleValueFunctionCallNode nodeIn) { var fieldNode = nodeIn.Parameters.ElementAt(0); if (string.Equals(nodeIn.Name, "empty", StringComparison.OrdinalIgnoreCase)) { - return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Empty, FilterValue.Null); + return ClrFilter.Empty(PropertyPathVisitor.Visit(fieldNode)); } var valueNode = nodeIn.Parameters.ElementAt(1); @@ -61,36 +61,36 @@ namespace Squidex.Infrastructure.Queries.OData { var value = ConstantWithTypeVisitor.Visit(valueNode); - return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.EndsWith, value); + return ClrFilter.EndsWith(PropertyPathVisitor.Visit(fieldNode), value); } if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase)) { var value = ConstantWithTypeVisitor.Visit(valueNode); - return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.StartsWith, value); + return ClrFilter.StartsWith(PropertyPathVisitor.Visit(fieldNode), value); } if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase)) { var value = ConstantWithTypeVisitor.Visit(valueNode); - return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Contains, value); + return ClrFilter.Contains(PropertyPathVisitor.Visit(fieldNode), value); } throw new NotSupportedException(); } - public override FilterNode Visit(BinaryOperatorNode nodeIn) + public override FilterNode Visit(BinaryOperatorNode nodeIn) { if (nodeIn.OperatorKind == BinaryOperatorKind.And) { - return new FilterJunction(FilterJunctionType.And, nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); + return ClrFilter.And(nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); } if (nodeIn.OperatorKind == BinaryOperatorKind.Or) { - return new FilterJunction(FilterJunctionType.Or, nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); + return ClrFilter.Or(nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); } if (nodeIn.Left is SingleValueFunctionCallNode functionNode) @@ -99,12 +99,12 @@ namespace Squidex.Infrastructure.Queries.OData var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - if (value.ValueType == FilterValueType.Boolean && value.Value is bool booleanRight) + if (value.ValueType == ClrValueType.Boolean && value.Value is bool booleanRight) { if ((nodeIn.OperatorKind == BinaryOperatorKind.Equal && !booleanRight) || (nodeIn.OperatorKind == BinaryOperatorKind.NotEqual && booleanRight)) { - regexFilter = new FilterNegate(regexFilter); + regexFilter = ClrFilter.Not(regexFilter); } return regexFilter; @@ -116,42 +116,42 @@ namespace Squidex.Infrastructure.Queries.OData { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.NotEquals, value); + return ClrFilter.Ne(PropertyPathVisitor.Visit(nodeIn.Left), value); } if (nodeIn.OperatorKind == BinaryOperatorKind.Equal) { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.Equals, value); + return ClrFilter.Eq(PropertyPathVisitor.Visit(nodeIn.Left), value); } if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan) { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThan, value); + return ClrFilter.Lt(PropertyPathVisitor.Visit(nodeIn.Left), value); } if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual) { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThanOrEqual, value); + return ClrFilter.Le(PropertyPathVisitor.Visit(nodeIn.Left), value); } if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan) { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThan, value); + return ClrFilter.Gt(PropertyPathVisitor.Visit(nodeIn.Left), value); } if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual) { var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); - return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThanOrEqual, value); + return ClrFilter.Ge(PropertyPathVisitor.Visit(nodeIn.Left), value); } } diff --git a/src/Squidex.Infrastructure/Queries/OData/LimitExtensions.cs b/src/Squidex.Infrastructure/Queries/OData/LimitExtensions.cs index 68532b5b6..044a33080 100644 --- a/src/Squidex.Infrastructure/Queries/OData/LimitExtensions.cs +++ b/src/Squidex.Infrastructure/Queries/OData/LimitExtensions.cs @@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.Queries.OData { public static class LimitExtensions { - public static void ParseTake(this ODataUriParser query, Query result) + public static void ParseTake(this ODataUriParser query, ClrQuery result) { var top = query.ParseTop(); @@ -21,7 +21,7 @@ namespace Squidex.Infrastructure.Queries.OData } } - public static void ParseSkip(this ODataUriParser query, Query result) + public static void ParseSkip(this ODataUriParser query, ClrQuery result) { var skip = query.ParseSkip(); diff --git a/src/Squidex.Infrastructure/Queries/OData/SortBuilder.cs b/src/Squidex.Infrastructure/Queries/OData/SortBuilder.cs index e9b5f748b..b8a67ab50 100644 --- a/src/Squidex.Infrastructure/Queries/OData/SortBuilder.cs +++ b/src/Squidex.Infrastructure/Queries/OData/SortBuilder.cs @@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.Queries.OData { public static class SortBuilder { - public static void ParseSort(this ODataUriParser query, Query result) + public static void ParseSort(this ODataUriParser query, ClrQuery result) { var orderBy = query.ParseOrderBy(); diff --git a/src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs b/src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs index 72e12cb1b..ec10a2452 100644 --- a/src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs +++ b/src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs @@ -9,22 +9,22 @@ using System.Linq; namespace Squidex.Infrastructure.Queries { - public sealed class PascalCasePathConverter : TransformVisitor + public sealed class PascalCasePathConverter : TransformVisitor { - private static readonly PascalCasePathConverter Instance = new PascalCasePathConverter(); + private static readonly PascalCasePathConverter Instance = new PascalCasePathConverter(); private PascalCasePathConverter() { } - public static FilterNode Transform(FilterNode node) + public static FilterNode Transform(FilterNode node) { return node.Accept(Instance); } - public override FilterNode Visit(FilterComparison nodeIn) + public override FilterNode Visit(CompareFilter nodeIn) { - return new FilterComparison(nodeIn.Lhs.Select(x => x.ToPascalCase()).ToList(), nodeIn.Operator, nodeIn.Rhs); + return new CompareFilter(nodeIn.Path.Select(x => x.ToPascalCase()).ToList(), nodeIn.Operator, nodeIn.Value); } } } diff --git a/src/Squidex.Infrastructure/Queries/PropertyPath.cs b/src/Squidex.Infrastructure/Queries/PropertyPath.cs new file mode 100644 index 000000000..176bccf3d --- /dev/null +++ b/src/Squidex.Infrastructure/Queries/PropertyPath.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Squidex.Infrastructure.Queries +{ + public sealed class PropertyPath : ReadOnlyCollection + { + private static readonly char[] Separators = { '.', '/' }; + + public PropertyPath(IList items) + : base(items) + { + if (items.Count == 0) + { + throw new ArgumentException("Path cannot be empty.", nameof(items)); + } + } + + public static implicit operator PropertyPath(string path) + { + return new PropertyPath(path?.Split(Separators, StringSplitOptions.RemoveEmptyEntries)?.ToList()); + } + + public static implicit operator PropertyPath(string[] path) + { + return new PropertyPath(path?.ToList()); + } + + public static implicit operator PropertyPath(List path) + { + return new PropertyPath(path); + } + + public static implicit operator PropertyPath(ImmutableList path) + { + return new PropertyPath(path); + } + + public override string ToString() + { + return string.Join(".", this); + } + } +} diff --git a/src/Squidex.Infrastructure/Queries/Query.cs b/src/Squidex.Infrastructure/Queries/Query.cs index 4281854d5..83dc03ff3 100644 --- a/src/Squidex.Infrastructure/Queries/Query.cs +++ b/src/Squidex.Infrastructure/Queries/Query.cs @@ -9,9 +9,9 @@ using System.Collections.Generic; namespace Squidex.Infrastructure.Queries { - public sealed class Query + public class Query { - public FilterNode Filter { get; set; } + public FilterNode Filter { get; set; } public string FullText { get; set; } diff --git a/src/Squidex.Infrastructure/Queries/SortNode.cs b/src/Squidex.Infrastructure/Queries/SortNode.cs index 030b0bafe..31e717f27 100644 --- a/src/Squidex.Infrastructure/Queries/SortNode.cs +++ b/src/Squidex.Infrastructure/Queries/SortNode.cs @@ -5,20 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; - namespace Squidex.Infrastructure.Queries { public sealed class SortNode { - public IReadOnlyList Path { get; } + public PropertyPath Path { get; } public SortOrder SortOrder { get; set; } - public SortNode(IReadOnlyList path, SortOrder sortOrder) + public SortNode(PropertyPath path, SortOrder sortOrder) { Guard.NotNull(path, nameof(path)); - Guard.NotEmpty(path, nameof(path)); Guard.Enum(sortOrder, nameof(sortOrder)); Path = path; diff --git a/src/Squidex.Infrastructure/Queries/TransformVisitor.cs b/src/Squidex.Infrastructure/Queries/TransformVisitor.cs index 38f60a4ff..d71e20403 100644 --- a/src/Squidex.Infrastructure/Queries/TransformVisitor.cs +++ b/src/Squidex.Infrastructure/Queries/TransformVisitor.cs @@ -9,21 +9,21 @@ using System.Linq; namespace Squidex.Infrastructure.Queries { - public abstract class TransformVisitor : FilterNodeVisitor + public abstract class TransformVisitor : FilterNodeVisitor, TValue> { - public override FilterNode Visit(FilterComparison nodeIn) + public override FilterNode Visit(CompareFilter nodeIn) { return nodeIn; } - public override FilterNode Visit(FilterJunction nodeIn) + public override FilterNode Visit(LogicalFilter nodeIn) { - return new FilterJunction(nodeIn.JunctionType, nodeIn.Operands.Select(x => x.Accept(this)).ToList()); + return new LogicalFilter(nodeIn.Type, nodeIn.Filters.Select(x => x.Accept(this)).ToList()); } - public override FilterNode Visit(FilterNegate nodeIn) + public override FilterNode Visit(NegateFilter nodeIn) { - return new FilterNegate(nodeIn.Operand.Accept(this)); + return new NegateFilter(nodeIn.Filter.Accept(this)); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs index 2ca9007f0..1e8471e88 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs @@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Assets var enriched1 = new AssetEntity(); var enriched2 = new AssetEntity(); - A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.Ignored)) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.Ignored)) .Returns(ResultList.CreateFrom(8, found1, found2)); A.CallTo(() => assetEnricher.EnrichAsync(A>.That.IsSameSequenceAs(found1, found2))) @@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Assets await sut.QueryAsync(requestContext, query); - A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Is("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Is("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending"))) .MustHaveHappened(); } @@ -146,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities.Assets await sut.QueryAsync(requestContext, query); - A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Is("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Is("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending"))) .MustHaveHappened(); } @@ -157,7 +157,8 @@ namespace Squidex.Domain.Apps.Entities.Assets await sut.QueryAsync(requestContext, query); - A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Is("Take: 30; Sort: lastModified Descending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, + A.That.Is("Take: 30; Sort: lastModified Descending"))) .MustHaveHappened(); } @@ -168,7 +169,8 @@ namespace Squidex.Domain.Apps.Entities.Assets await sut.QueryAsync(requestContext, query); - A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, + A.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"))) .MustHaveHappened(); } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs index b19c1bb51..ba2ab8732 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs @@ -17,7 +17,7 @@ using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Xunit; -using FilterBuilder = Squidex.Infrastructure.Queries.FilterBuilder; +using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; namespace Squidex.Domain.Apps.Entities.Assets.MongoDb @@ -35,13 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_throw_exception_for_full_text_search() { - Assert.Throws(() => Q(new Query { FullText = "Full Text" })); + Assert.Throws(() => Q(new ClrQuery { FullText = "Full Text" })); } [Fact] public void Should_make_query_with_lastModified() { - var i = F(FilterBuilder.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var i = F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var o = C("{ 'LastModified' : ISODate('1988-01-19T12:00:00Z') }"); Assert.Equal(o, i); @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_lastModifiedBy() { - var i = F(FilterBuilder.Eq("lastModifiedBy", "Me")); + var i = F(ClrFilter.Eq("lastModifiedBy", "Me")); var o = C("{ 'LastModifiedBy' : 'Me' }"); Assert.Equal(o, i); @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_created() { - var i = F(FilterBuilder.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var i = F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var o = C("{ 'Created' : ISODate('1988-01-19T12:00:00Z') }"); Assert.Equal(o, i); @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_createdBy() { - var i = F(FilterBuilder.Eq("createdBy", "Me")); + var i = F(ClrFilter.Eq("createdBy", "Me")); var o = C("{ 'CreatedBy' : 'Me' }"); Assert.Equal(o, i); @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_version() { - var i = F(FilterBuilder.Eq("version", 0)); + var i = F(ClrFilter.Eq("version", 0)); var o = C("{ 'Version' : NumberLong(0) }"); Assert.Equal(o, i); @@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_fileVersion() { - var i = F(FilterBuilder.Eq("fileVersion", 2)); + var i = F(ClrFilter.Eq("fileVersion", 2)); var o = C("{ 'FileVersion' : NumberLong(2) }"); Assert.Equal(o, i); @@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_tags() { - var i = F(FilterBuilder.Eq("tags", "tag1")); + var i = F(ClrFilter.Eq("tags", "tag1")); var o = C("{ 'Tags' : 'tag1' }"); Assert.Equal(o, i); @@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_fileName() { - var i = F(FilterBuilder.Eq("fileName", "Logo.png")); + var i = F(ClrFilter.Eq("fileName", "Logo.png")); var o = C("{ 'FileName' : 'Logo.png' }"); Assert.Equal(o, i); @@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_isImage() { - var i = F(FilterBuilder.Eq("isImage", true)); + var i = F(ClrFilter.Eq("isImage", true)); var o = C("{ 'IsImage' : true }"); Assert.Equal(o, i); @@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_mimeType() { - var i = F(FilterBuilder.Eq("mimeType", "text/json")); + var i = F(ClrFilter.Eq("mimeType", "text/json")); var o = C("{ 'MimeType' : 'text/json' }"); Assert.Equal(o, i); @@ -131,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_fileSize() { - var i = F(FilterBuilder.Eq("fileSize", 1024)); + var i = F(ClrFilter.Eq("fileSize", 1024)); var o = C("{ 'FileSize' : NumberLong(1024) }"); Assert.Equal(o, i); @@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_pixelHeight() { - var i = F(FilterBuilder.Eq("pixelHeight", 600)); + var i = F(ClrFilter.Eq("pixelHeight", 600)); var o = C("{ 'PixelHeight' : 600 }"); Assert.Equal(o, i); @@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_query_with_pixelWidth() { - var i = F(FilterBuilder.Eq("pixelWidth", 800)); + var i = F(ClrFilter.Eq("pixelWidth", 800)); var o = C("{ 'PixelWidth' : 800 }"); Assert.Equal(o, i); @@ -176,7 +176,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_take_statement() { - var query = new Query { Take = 3 }; + var query = new ClrQuery { Take = 3 }; var cursor = A.Fake>(); cursor.AssetTake(query.AdjustToModel()); @@ -188,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public void Should_make_skip_statement() { - var query = new Query { Skip = 3 }; + var query = new ClrQuery { Skip = 3 }; var cursor = A.Fake>(); cursor.AssetSkip(query.AdjustToModel()); @@ -202,9 +202,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb return value.Replace('\'', '"'); } - private static string F(FilterNode filter) + private static string F(FilterNode filter) { - return Q(new Query { Filter = filter }); + return Q(new ClrQuery { Filter = filter }); } private static string S(params SortNode[] sorts) @@ -219,12 +219,12 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb i = sortDefinition.Render(Serializer, Registry).ToString(); }); - cursor.AssetSort(new Query { Sort = sorts.ToList() }.AdjustToModel()); + cursor.AssetSort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel()); return i; } - private static string Q(Query query) + private static string Q(ClrQuery query) { var rendered = query.AdjustToModel().BuildFilter(false).Filter diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs index ffb1495e3..c5fc77ec1 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A>.That.Contains("name1"))) .Returns(new Dictionary { ["name1"] = "id1" }); - var source = FilterBuilder.Eq("tags", "name1"); + var source = ClrFilter.Eq("tags", "name1"); var result = FilterTagTransformer.Transform(source, appId, tagService); Assert.Equal("tags == 'id1'", result.ToString()); @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A>.That.Contains("name1"))) .Returns(new Dictionary()); - var source = FilterBuilder.Eq("tags", "name1"); + var source = ClrFilter.Eq("tags", "name1"); var result = FilterTagTransformer.Transform(source, appId, tagService); Assert.Equal("tags == 'name1'", result.ToString()); @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries [Fact] public void Should_not_normalize_other_field() { - var source = FilterBuilder.Eq("other", "value"); + var source = ClrFilter.Eq("other", "value"); var result = FilterTagTransformer.Transform(source, appId, tagService); Assert.Equal("other == 'value'", result.ToString()); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index 6e4625ccf..9097bba36 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await sut.QueryAsync(requestContext, schemaId.Name, query); A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.Is(Status.Published), false, - A.That.Is("Take: 30; Sort: lastModified Descending"), false)) + A.That.Is("Take: 30; Sort: lastModified Descending"), false)) .MustHaveHappened(); } @@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await sut.QueryAsync(requestContext, schemaId.Name, query); A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.Is(status), false, - A.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"), false)) + A.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"), false)) .MustHaveHappened(); } @@ -482,7 +482,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private void SetupContents(Status[] status, int count, int total, IContentEntity content, bool inDraft, bool includeDraft) { - A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.Is(status), inDraft, A.Ignored, includeDraft)) + A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.Is(status), inDraft, A.Ignored, includeDraft)) .Returns(ResultList.Create(total, Enumerable.Repeat(content, count))); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs index 746dc12ae..8efdff446 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Linq; using FakeItEasy; using MongoDB.Bson.Serialization; @@ -23,7 +24,7 @@ using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Xunit; -using FilterBuilder = Squidex.Infrastructure.Queries.FilterBuilder; +using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; namespace Squidex.Domain.Apps.Entities.Contents.MongoDb @@ -78,13 +79,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_throw_exception_for_invalid_field() { - Assert.Throws(() => F(FilterBuilder.Eq("data/invalid/iv", "Me"))); + Assert.Throws(() => F(ClrFilter.Eq("data/invalid/iv", "Me"))); } [Fact] public void Should_make_query_with_lastModified() { - var i = F(FilterBuilder.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var i = F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var o = C("{ 'mt' : '1988-01-19T12:00:00Z' }"); Assert.Equal(o, i); @@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_lastModifiedBy() { - var i = F(FilterBuilder.Eq("lastModifiedBy", "Me")); + var i = F(ClrFilter.Eq("lastModifiedBy", "Me")); var o = C("{ 'mb' : 'Me' }"); Assert.Equal(o, i); @@ -102,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_created() { - var i = F(FilterBuilder.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var i = F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var o = C("{ 'ct' : '1988-01-19T12:00:00Z' }"); Assert.Equal(o, i); @@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_createdBy() { - var i = F(FilterBuilder.Eq("createdBy", "Me")); + var i = F(ClrFilter.Eq("createdBy", "Me")); var o = C("{ 'cb' : 'Me' }"); Assert.Equal(o, i); @@ -120,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_version() { - var i = F(FilterBuilder.Eq("version", 0L)); + var i = F(ClrFilter.Eq("version", 0L)); var o = C("{ 'vs' : NumberLong(0) }"); Assert.Equal(o, i); @@ -129,7 +130,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_version_and_list() { - var i = F(FilterBuilder.In("version", 0L, 2L, 5L)); + var i = F(ClrFilter.In("version", new List { 0L, 2L, 5L })); var o = C("{ 'vs' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }"); Assert.Equal(o, i); @@ -138,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_from_draft() { - var i = F(FilterBuilder.Eq("data/dashed_field/iv", "Value"), true); + var i = F(ClrFilter.Eq("data/dashed_field/iv", "Value"), true); var o = C("{ 'dd.8.iv' : 'Value' }"); Assert.Equal(o, i); @@ -147,7 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_empty_test() { - var i = F(FilterBuilder.Empty("data/firstName/iv"), true); + var i = F(ClrFilter.Empty("data/firstName/iv"), true); var o = C("{ '$or' : [{ 'dd.1.iv' : { '$exists' : false } }, { 'dd.1.iv' : null }, { 'dd.1.iv' : '' }, { 'dd.1.iv' : [] }] }"); Assert.Equal(o, i); @@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_datetime_data() { - var i = F(FilterBuilder.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var i = F(ClrFilter.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var o = C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }"); Assert.Equal(o, i); @@ -165,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_underscore_field() { - var i = F(FilterBuilder.Eq("data/dashed_field/iv", "Value")); + var i = F(ClrFilter.Eq("data/dashed_field/iv", "Value")); var o = C("{ 'do.8.iv' : 'Value' }"); Assert.Equal(o, i); @@ -174,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_references_equals() { - var i = F(FilterBuilder.Eq("data/friends/iv", "guid")); + var i = F(ClrFilter.Eq("data/friends/iv", "guid")); var o = C("{ 'do.7.iv' : 'guid' }"); Assert.Equal(o, i); @@ -183,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_array_field() { - var i = F(FilterBuilder.Eq("data/hobbies/iv/name", "PC")); + var i = F(ClrFilter.Eq("data/hobbies/iv/name", "PC")); var o = C("{ 'do.9.iv.91' : 'PC' }"); Assert.Equal(o, i); @@ -192,7 +193,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_assets_equals() { - var i = F(FilterBuilder.Eq("data/pictures/iv", "guid")); + var i = F(ClrFilter.Eq("data/pictures/iv", "guid")); var o = C("{ 'do.6.iv' : 'guid' }"); Assert.Equal(o, i); @@ -201,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_full_text() { - var i = Q(new Query { FullText = "Hello my World" }); + var i = Q(new ClrQuery { FullText = "Hello my World" }); var o = C("{ '$text' : { '$search' : 'Hello my World' } }"); Assert.Equal(o, i); @@ -228,7 +229,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_take_statement() { - var query = new Query { Take = 3 }; + var query = new ClrQuery { Take = 3 }; var cursor = A.Fake>(); cursor.ContentTake(query.AdjustToModel(schemaDef, false)); @@ -240,7 +241,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_skip_statement() { - var query = new Query { Skip = 3 }; + var query = new ClrQuery { Skip = 3 }; var cursor = A.Fake>(); cursor.ContentSkip(query.AdjustToModel(schemaDef, false)); @@ -254,9 +255,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb return value.Replace('\'', '"'); } - private string F(FilterNode filter, bool useDraft = false) + private string F(FilterNode filter, bool useDraft = false) { - return Q(new Query { Filter = filter }, useDraft); + return Q(new ClrQuery { Filter = filter }, useDraft); } private string S(params SortNode[] sorts) @@ -271,12 +272,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb i = sortDefinition.Render(Serializer, Registry).ToString(); }); - cursor.ContentSort(new Query { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false)); + cursor.ContentSort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false)); return i; } - private string Q(Query query, bool useDraft = false) + private string Q(ClrQuery query, bool useDraft = false) { var rendered = query.AdjustToModel(schemaDef, useDraft).BuildFilter().Filter diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs index 4d8d0201e..a23603668 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs @@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Schemas(schemaId.Id), A>.That.Contains("name1"))) .Returns(new Dictionary { ["name1"] = "id1" }); - var source = FilterBuilder.Eq("data.tags2.iv", "name1"); + var source = ClrFilter.Eq("data.tags2.iv", "name1"); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); Assert.Equal("data.tags2.iv == 'id1'", result.ToString()); @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A>.That.Contains("name1"))) .Returns(new Dictionary()); - var source = FilterBuilder.Eq("data.tags2.iv", "name1"); + var source = ClrFilter.Eq("data.tags2.iv", "name1"); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); Assert.Equal("data.tags2.iv == 'name1'", result.ToString()); @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries [Fact] public void Should_not_normalize_other_tags_field() { - var source = FilterBuilder.Eq("data.tags1.iv", "value"); + var source = ClrFilter.Eq("data.tags1.iv", "value"); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); Assert.Equal("data.tags1.iv == 'value'", result.ToString()); @@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries [Fact] public void Should_not_normalize_other_typed_field() { - var source = FilterBuilder.Eq("data.string.iv", "value"); + var source = ClrFilter.Eq("data.string.iv", "value"); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); Assert.Equal("data.string.iv == 'value'", result.ToString()); @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries [Fact] public void Should_not_normalize_non_data_field() { - var source = FilterBuilder.Eq("no.data", "value"); + var source = ClrFilter.Eq("no.data", "value"); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); Assert.Equal("no.data == 'value'", result.ToString()); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AExtensions.cs b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AExtensions.cs index e038caead..5ec9b5fce 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AExtensions.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AExtensions.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers { public static class AExtensions { - public static Query Is(this INegatableArgumentConstraintManager that, string query) + public static ClrQuery Is(this INegatableArgumentConstraintManager that, string query) { return that.Matches(x => x.ToString() == query); } diff --git a/tests/Squidex.Infrastructure.Tests/Queries/PascalCasePathConverterTests.cs b/tests/Squidex.Infrastructure.Tests/Queries/PascalCasePathConverterTests.cs index 3bde017f9..3c1b86e7f 100644 --- a/tests/Squidex.Infrastructure.Tests/Queries/PascalCasePathConverterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Queries/PascalCasePathConverterTests.cs @@ -14,8 +14,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_convert_property() { - var source = FilterBuilder.Eq("property", 1); - var result = PascalCasePathConverter.Transform(source); + var source = ClrFilter.Eq("property", 1); + var result = PascalCasePathConverter.Transform(source); Assert.Equal("Property == 1", result.ToString()); } @@ -23,8 +23,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_convert_properties() { - var source = FilterBuilder.Eq("root.child", 1); - var result = PascalCasePathConverter.Transform(source); + var source = ClrFilter.Eq("root.child", 1); + var result = PascalCasePathConverter.Transform(source); Assert.Equal("Root.Child == 1", result.ToString()); } diff --git a/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonTests.cs b/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonTests.cs new file mode 100644 index 000000000..508c3b676 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonTests.cs @@ -0,0 +1,181 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Newtonsoft.Json; +using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.TestHelpers; +using Xunit; + +namespace Squidex.Infrastructure.Queries +{ + public class QueryJsonTests + { + [Theory] + [InlineData("eq", "property == 12")] + [InlineData("ne", "property != 12")] + [InlineData("le", "property <= 12")] + [InlineData("lt", "property < 12")] + [InlineData("ge", "property >= 12")] + [InlineData("gt", "property > 12")] + [InlineData("contains", "contains(property, 12)")] + [InlineData("endswith", "endsWith(property, 12)")] + [InlineData("startswith", "startsWith(property, 12)")] + public void Should_convert_comparison(string op, string expected) + { + var json = new + { + path = "property", + op, + value = 12 + }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal(expected, filter.ToString()); + } + + [Fact] + public void Should_convert_comparison_empty() + { + var json = new { path = "property", op = "empty" }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal("empty(property)", filter.ToString()); + } + + [Fact] + public void Should_convert_comparison_in() + { + var json = new { path = "property", op = "in", value = new[] { 12, 13 } }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal("property in [12, 13]", filter.ToString()); + } + + [Fact] + public void Should_convert_comparison_with_deep_path() + { + var json = new { path = "property.nested", op = "eq", value = 12 }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal("property.nested == 12", filter.ToString()); + } + + [Fact] + public void Should_convert_logical_and() + { + var json = new + { + and = new[] + { + new { path = "property", op = "ge", value = 10 }, + new { path = "property", op = "lt", value = 20 } + } + }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal("(property >= 10 && property < 20)", filter.ToString()); + } + + [Fact] + public void Should_convert_logical_or() + { + var json = new + { + or = new[] + { + new { path = "property", op = "ge", value = 10 }, + new { path = "property", op = "lt", value = 20 } + } + }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal("(property >= 10 || property < 20)", filter.ToString()); + } + + [Fact] + public void Should_convert_logical_not() + { + var json = new + { + not = new { path = "property", op = "ge", value = 10 } + }; + + var filter = SerializeAndDeserialize(json); + + Assert.Equal("!(property >= 10)", filter.ToString()); + } + + [Fact] + public void Should_throw_exception_for_invalid_operator() + { + var json = new { path = "property", op = "invalid", value = 12 }; + + Assert.ThrowsAny(() => SerializeAndDeserialize(json)); + } + + [Fact] + public void Should_throw_exception_for_missing_path() + { + var json = new { op = "invalid", value = 12 }; + + Assert.ThrowsAny(() => SerializeAndDeserialize(json)); + } + + [Fact] + public void Should_throw_exception_for_missing_operator() + { + var json = new { path = "property", value = 12 }; + + Assert.ThrowsAny(() => SerializeAndDeserialize(json)); + } + + [Fact] + public void Should_throw_exception_for_missing_value() + { + var json = new { path = "property", op = "invalid" }; + + Assert.ThrowsAny(() => SerializeAndDeserialize(json)); + } + + [Fact] + public void Should_throw_exception_for_invalid_property() + { + var json = new { path = "property", op = "invalid", value = 12, other = 4 }; + + Assert.ThrowsAny(() => SerializeAndDeserialize(json)); + } + + [Fact] + public void Should_throw_exception_for_invalid_property_after_filter() + { + var json = new + { + and = new[] + { + new { path = "property", op = "ge", value = 10 }, + new { path = "property", op = "lt", value = 20 } + }, + additional = 1 + }; + + Assert.ThrowsAny(() => SerializeAndDeserialize(json)); + } + + private static FilterNode SerializeAndDeserialize(T value) + { + var json = JsonHelper.DefaultSerializer.Serialize(value, true); + + return JsonHelper.DefaultSerializer.Deserialize>(json); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs b/tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs similarity index 99% rename from tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs rename to tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs index 90154a9c5..bdac075d4 100644 --- a/tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs @@ -11,11 +11,11 @@ using Xunit; namespace Squidex.Infrastructure.Queries { - public class ODataConversionTests + public class QueryODataConversionTests { private static readonly IEdmModel EdmModel; - static ODataConversionTests() + static QueryODataConversionTests() { var entityType = new EdmEntityType("Squidex", "Users"); diff --git a/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs index bef611a12..ad9db8569 100644 --- a/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft; +using Squidex.Infrastructure.Queries.Json; namespace Squidex.Infrastructure.TestHelpers { @@ -34,11 +35,13 @@ namespace Squidex.Infrastructure.TestHelpers new ClaimsPrincipalConverter(), new InstantConverter(), new EnvelopeHeadersConverter(), + new FilterConverter(), new JsonValueConverter(), new LanguageConverter(), new NamedGuidIdConverter(), new NamedLongIdConverter(), new NamedStringIdConverter(), + new PropertyPathConverter(), new RefTokenConverter(), new StringEnumConverter()),