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