mirror of https://github.com/Squidex/squidex.git
12 changed files with 391 additions and 29 deletions
@ -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,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