mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
65 changed files with 1030 additions and 618 deletions
@ -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<ClrValue> And(params FilterNode<ClrValue>[] filters) |
|||
{ |
|||
return new LogicalFilter<ClrValue>(LogicalFilterType.And, filters); |
|||
} |
|||
|
|||
public static LogicalFilter<ClrValue> Or(params FilterNode<ClrValue>[] filters) |
|||
{ |
|||
return new LogicalFilter<ClrValue>(LogicalFilterType.Or, filters); |
|||
} |
|||
|
|||
public static NegateFilter<ClrValue> Not(FilterNode<ClrValue> filter) |
|||
{ |
|||
return new NegateFilter<ClrValue>(filter); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Eq(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.Equals, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Ne(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.NotEquals, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Lt(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.LessThan, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Le(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.LessThanOrEqual, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Gt(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.GreaterThan, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Ge(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.GreaterThanOrEqual, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Contains(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.Contains, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> EndsWith(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.EndsWith, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> StartsWith(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.StartsWith, value); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> Empty(PropertyPath path) |
|||
{ |
|||
return Binary(path, CompareOperator.Empty, null); |
|||
} |
|||
|
|||
public static CompareFilter<ClrValue> In(PropertyPath path, ClrValue value) |
|||
{ |
|||
return Binary(path, CompareOperator.In, value); |
|||
} |
|||
|
|||
private static CompareFilter<ClrValue> Binary(PropertyPath path, CompareOperator @operator, ClrValue value) |
|||
{ |
|||
return new CompareFilter<ClrValue>(path, @operator, value ?? ClrValue.Null); |
|||
} |
|||
} |
|||
} |
|||
@ -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<ClrValue> |
|||
{ |
|||
} |
|||
} |
|||
@ -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<Instant> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Instant, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<Guid> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Guid, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<bool> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Boolean, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<float> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Single, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<double> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Double, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<int> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Int32, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<long> value) |
|||
{ |
|||
return value != null ? new ClrValue(value, ClrValueType.Int64, true) : Null; |
|||
} |
|||
|
|||
public static implicit operator ClrValue(List<string> 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<object>().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); |
|||
} |
|||
} |
|||
} |
|||
@ -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<TValue> : FilterNode<TValue> |
|||
{ |
|||
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<T>(FilterNodeVisitor<T, TValue> 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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
} |
|||
@ -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<string> Lhs { get; } |
|||
|
|||
public FilterOperator Operator { get; } |
|||
|
|||
public FilterValue Rhs { get; } |
|||
|
|||
public FilterComparison(IReadOnlyList<string> 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<T>(FilterNodeVisitor<T> 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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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<FilterNode> Operands { get; } |
|||
|
|||
public FilterJunctionType JunctionType { get; } |
|||
|
|||
public FilterJunction(FilterJunctionType junctionType, IReadOnlyList<FilterNode> 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<T>(FilterNodeVisitor<T> visitor) |
|||
{ |
|||
return visitor.Visit(this); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"({string.Join(JunctionType == FilterJunctionType.And ? " && " : " || ", Operands)})"; |
|||
} |
|||
} |
|||
} |
|||
@ -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<Guid> value) |
|||
: this(value, FilterValueType.Guid, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<Instant> value) |
|||
: this(value, FilterValueType.Instant, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<bool> value) |
|||
: this(value, FilterValueType.Boolean, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<float> value) |
|||
: this(value, FilterValueType.Single, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<double> value) |
|||
: this(value, FilterValueType.Double, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<int> value) |
|||
: this(value, FilterValueType.Int32, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<long> value) |
|||
: this(value, FilterValueType.Int64, true) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
} |
|||
|
|||
public FilterValue(List<string> 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<object>().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); |
|||
} |
|||
} |
|||
} |
|||
@ -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<FilterNode<IJsonValue>> |
|||
{ |
|||
public override IEnumerable<Type> SupportedTypes |
|||
{ |
|||
get |
|||
{ |
|||
yield return typeof(CompareFilter<IJsonValue>); |
|||
yield return typeof(FilterNode<IJsonValue>); |
|||
yield return typeof(LogicalFilter<IJsonValue>); |
|||
yield return typeof(NegateFilter<IJsonValue>); |
|||
} |
|||
} |
|||
|
|||
public override bool CanConvert(Type objectType) |
|||
{ |
|||
return SupportedTypes.Contains(objectType); |
|||
} |
|||
|
|||
protected override FilterNode<IJsonValue> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) |
|||
{ |
|||
if (reader.TokenType != JsonToken.StartObject) |
|||
{ |
|||
throw new JsonException($"Expected StartObject, but got {reader.TokenType}."); |
|||
} |
|||
|
|||
FilterNode<IJsonValue> 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<FilterNode<IJsonValue>>(reader); |
|||
|
|||
result = new NegateFilter<IJsonValue>(filter); |
|||
break; |
|||
case "and": |
|||
var andFilters = serializer.Deserialize<List<FilterNode<IJsonValue>>>(reader); |
|||
|
|||
result = new LogicalFilter<IJsonValue>(LogicalFilterType.And, andFilters); |
|||
break; |
|||
case "or": |
|||
var orFilters = serializer.Deserialize<List<FilterNode<IJsonValue>>>(reader); |
|||
|
|||
result = new LogicalFilter<IJsonValue>(LogicalFilterType.Or, orFilters); |
|||
break; |
|||
case "path": |
|||
comparePath = serializer.Deserialize<PropertyPath>(reader); |
|||
break; |
|||
case "op": |
|||
compareOperator = ReadOperator(reader, serializer); |
|||
break; |
|||
case "value": |
|||
compareValue = serializer.Deserialize<IJsonValue>(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<IJsonValue>(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<string>(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<IJsonValue> value, JsonSerializer serializer) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -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<PropertyPath> |
|||
{ |
|||
protected override PropertyPath ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) |
|||
{ |
|||
var value = serializer.Deserialize<string>(reader); |
|||
|
|||
return value; |
|||
} |
|||
|
|||
protected override void WriteValue(JsonWriter writer, PropertyPath value, JsonSerializer serializer) |
|||
{ |
|||
serializer.Serialize(writer, (IEnumerable<string>)value); |
|||
} |
|||
} |
|||
} |
|||
@ -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<TValue> : FilterNode<TValue> |
|||
{ |
|||
public IReadOnlyList<FilterNode<TValue>> Filters { get; } |
|||
|
|||
public LogicalFilterType Type { get; } |
|||
|
|||
public LogicalFilter(LogicalFilterType type, IReadOnlyList<FilterNode<TValue>> 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<T>(FilterNodeVisitor<T, TValue> visitor) |
|||
{ |
|||
return visitor.Visit(this); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"({string.Join(Type == LogicalFilterType.And ? " && " : " || ", Filters)})"; |
|||
} |
|||
} |
|||
} |
|||
@ -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<string> |
|||
{ |
|||
private static readonly char[] Separators = { '.', '/' }; |
|||
|
|||
public PropertyPath(IList<string> 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<string> path) |
|||
{ |
|||
return new PropertyPath(path); |
|||
} |
|||
|
|||
public static implicit operator PropertyPath(ImmutableList<string> path) |
|||
{ |
|||
return new PropertyPath(path); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return string.Join(".", this); |
|||
} |
|||
} |
|||
} |
|||
@ -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<JsonException>(() => SerializeAndDeserialize(json)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_for_missing_path() |
|||
{ |
|||
var json = new { op = "invalid", value = 12 }; |
|||
|
|||
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_for_missing_operator() |
|||
{ |
|||
var json = new { path = "property", value = 12 }; |
|||
|
|||
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_for_missing_value() |
|||
{ |
|||
var json = new { path = "property", op = "invalid" }; |
|||
|
|||
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_for_invalid_property() |
|||
{ |
|||
var json = new { path = "property", op = "invalid", value = 12, other = 4 }; |
|||
|
|||
Assert.ThrowsAny<JsonException>(() => 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<JsonException>(() => SerializeAndDeserialize(json)); |
|||
} |
|||
|
|||
private static FilterNode<IJsonValue> SerializeAndDeserialize<T>(T value) |
|||
{ |
|||
var json = JsonHelper.DefaultSerializer.Serialize(value, true); |
|||
|
|||
return JsonHelper.DefaultSerializer.Deserialize<FilterNode<IJsonValue>>(json); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue