Skip to content

Commit 26d9dcd

Browse files
committed
implement base filtering
1 parent 659ab38 commit 26d9dcd

21 files changed

+837
-4
lines changed

Kontent.Ai.Delivery.Abstractions/Kontent.Ai.Delivery.Abstractions.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
<None Include="../README.md" Pack="true" PackagePath="" />
2929
<None Include="../kai-logo-symbol-color-rgb.png" Pack="true" PackagePath="" />
3030
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
31-
<!-- Avoid introducing any non-framework dependencies -->
31+
<PackageReference Include="OneOf" Version="3.0.271" />
32+
<!-- OneOf for type-safe filter unions -->
3233
</ItemGroup>
3334

3435
</Project>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Linq;
3+
using System.Text.Encodings.Web;
4+
5+
namespace Kontent.Ai.Delivery.Abstractions.QueryBuilders.Filtering;
6+
7+
/// <summary>
8+
/// Concrete implementation of IFilter that represents a filtering operation.
9+
/// </summary>
10+
public sealed record Filter(
11+
string PropertyPath,
12+
FilterOperator Operator,
13+
FilterValue Value) : IFilter
14+
{
15+
/// <summary>
16+
/// Serializes this filter to the Kontent.ai API query parameter format.
17+
/// </summary>
18+
/// <returns>The serialized filter string.</returns>
19+
public string ToQueryParameter()
20+
{
21+
var operatorSuffix = Operator switch
22+
{
23+
FilterOperator.Equals => "[eq]",
24+
FilterOperator.NotEquals => "[neq]",
25+
FilterOperator.LessThan => "[lt]",
26+
FilterOperator.LessThanOrEqual => "[lte]",
27+
FilterOperator.GreaterThan => "[gt]",
28+
FilterOperator.GreaterThanOrEqual => "[gte]",
29+
FilterOperator.Range => "[range]",
30+
FilterOperator.In => "[in]",
31+
FilterOperator.NotIn => "[nin]",
32+
FilterOperator.Contains => "[contains]",
33+
FilterOperator.Any => "[any]",
34+
FilterOperator.All => "[all]",
35+
FilterOperator.Empty => "[empty]",
36+
FilterOperator.NotEmpty => "[nempty]",
37+
_ => throw new ArgumentOutOfRangeException(nameof(Operator), Operator, null)
38+
};
39+
40+
// Empty operators don't need values
41+
if (Operator is FilterOperator.Empty or FilterOperator.NotEmpty)
42+
{
43+
return $"{PropertyPath}{operatorSuffix}";
44+
}
45+
46+
var serializedValue = Value.Serialize();
47+
48+
return $"{PropertyPath}{operatorSuffix}={serializedValue}";
49+
}
50+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
namespace Kontent.Ai.Delivery.Abstractions.QueryBuilders.Filtering;
2+
3+
/// <summary>
4+
/// All available filtering operators in the Kontent.ai Delivery API.
5+
/// </summary>
6+
public enum FilterOperator
7+
{
8+
/// <summary>
9+
/// Exact match [eq].
10+
/// </summary>
11+
Equals,
12+
13+
/// <summary>
14+
/// Does not match [neq].
15+
/// </summary>
16+
NotEquals,
17+
18+
/// <summary>
19+
/// Less than specified value [lt].
20+
/// </summary>
21+
LessThan,
22+
23+
/// <summary>
24+
/// Less than or equal to specified value [lte].
25+
/// </summary>
26+
LessThanOrEqual,
27+
28+
/// <summary>
29+
/// Greater than specified value [gt].
30+
/// </summary>
31+
GreaterThan,
32+
33+
/// <summary>
34+
/// Greater than or equal to specified value [gte].
35+
/// </summary>
36+
GreaterThanOrEqual,
37+
38+
/// <summary>
39+
/// Within specified range (inclusive) [range].
40+
/// </summary>
41+
Range,
42+
43+
/// <summary>
44+
/// Matches any of specified values [in].
45+
/// </summary>
46+
In,
47+
48+
/// <summary>
49+
/// Does not match any of specified values [nin].
50+
/// </summary>
51+
NotIn,
52+
53+
/// <summary>
54+
/// String contains specified text or array contains specified value [contains].
55+
/// </summary>
56+
Contains,
57+
58+
/// <summary>
59+
/// Array contains any of specified values [any].
60+
/// </summary>
61+
Any,
62+
63+
/// <summary>
64+
/// Array contains all specified values [all].
65+
/// </summary>
66+
All,
67+
68+
/// <summary>
69+
/// Field is empty/null [empty].
70+
/// </summary>
71+
Empty,
72+
73+
/// <summary>
74+
/// Field is not empty/null [nempty].
75+
/// </summary>
76+
NotEmpty
77+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace Kontent.Ai.Delivery.Abstractions.QueryBuilders.Filtering;
5+
6+
/// <summary>
7+
/// Represents a filter value that can be a single value, multiple values, a range, or empty.
8+
/// </summary>
9+
public abstract class FilterValue
10+
{
11+
/// <summary>
12+
/// Serializes this filter value to a string format suitable for the API.
13+
/// </summary>
14+
/// <returns>The serialized value string.</returns>
15+
public abstract string Serialize();
16+
17+
/// <summary>
18+
/// Represents an empty filter value for operators that don't require values.
19+
/// </summary>
20+
public sealed class Empty : FilterValue
21+
{
22+
internal Empty() { }
23+
public override string Serialize() => "";
24+
}
25+
26+
/// <summary>
27+
/// Represents a single string value.
28+
/// </summary>
29+
public sealed class StringValue : FilterValue
30+
{
31+
public string Value { get; }
32+
internal StringValue(string value) => Value = value;
33+
public override string Serialize() => System.Text.Encodings.Web.UrlEncoder.Default.Encode(Value);
34+
}
35+
36+
/// <summary>
37+
/// Represents a single integer value.
38+
/// </summary>
39+
public sealed class IntValue : FilterValue
40+
{
41+
public int Value { get; }
42+
internal IntValue(int value) => Value = value;
43+
public override string Serialize() => Value.ToString();
44+
}
45+
46+
/// <summary>
47+
/// Represents a single DateTime value.
48+
/// </summary>
49+
public sealed class DateTimeValue : FilterValue
50+
{
51+
public DateTime Value { get; }
52+
internal DateTimeValue(DateTime value) => Value = value;
53+
public override string Serialize() => Value.ToString("yyyy-MM-ddTHH:mm:ssZ");
54+
}
55+
56+
/// <summary>
57+
/// Represents a single boolean value.
58+
/// </summary>
59+
public sealed class BooleanValue : FilterValue
60+
{
61+
public bool Value { get; }
62+
internal BooleanValue(bool value) => Value = value;
63+
public override string Serialize() => Value.ToString().ToLowerInvariant();
64+
}
65+
66+
/// <summary>
67+
/// Represents multiple string values.
68+
/// </summary>
69+
public sealed class StringArrayValue : FilterValue
70+
{
71+
public string[] Value { get; }
72+
internal StringArrayValue(string[] value) => Value = value;
73+
public override string Serialize() => string.Join(",", Value.Select(System.Text.Encodings.Web.UrlEncoder.Default.Encode));
74+
}
75+
76+
/// <summary>
77+
/// Represents multiple integer values.
78+
/// </summary>
79+
public sealed class IntArrayValue : FilterValue
80+
{
81+
public int[] Value { get; }
82+
internal IntArrayValue(int[] value) => Value = value;
83+
public override string Serialize() => string.Join(",", Value.Select(v => v.ToString()));
84+
}
85+
86+
/// <summary>
87+
/// Represents multiple DateTime values.
88+
/// </summary>
89+
public sealed class DateTimeArrayValue : FilterValue
90+
{
91+
public DateTime[] Value { get; }
92+
internal DateTimeArrayValue(DateTime[] value) => Value = value;
93+
public override string Serialize() => string.Join(",", Value.Select(v => v.ToString("yyyy-MM-ddTHH:mm:ssZ")));
94+
}
95+
96+
/// <summary>
97+
/// Represents a string range with lower and upper bounds.
98+
/// </summary>
99+
public sealed class StringRange : FilterValue
100+
{
101+
public string Lower { get; }
102+
public string Upper { get; }
103+
internal StringRange(string lower, string upper) => (Lower, Upper) = (lower, upper);
104+
public override string Serialize() => $"{System.Text.Encodings.Web.UrlEncoder.Default.Encode(Lower)},{System.Text.Encodings.Web.UrlEncoder.Default.Encode(Upper)}";
105+
}
106+
107+
/// <summary>
108+
/// Represents a numeric range with lower and upper bounds.
109+
/// </summary>
110+
public sealed class NumericRange : FilterValue
111+
{
112+
public int Lower { get; }
113+
public int Upper { get; }
114+
internal NumericRange(int lower, int upper) => (Lower, Upper) = (lower, upper);
115+
public override string Serialize() => $"{Lower},{Upper}";
116+
}
117+
118+
/// <summary>
119+
/// Represents a date range with lower and upper bounds.
120+
/// </summary>
121+
public sealed class DateRange : FilterValue
122+
{
123+
public DateTime Lower { get; }
124+
public DateTime Upper { get; }
125+
internal DateRange(DateTime lower, DateTime upper) => (Lower, Upper) = (lower, upper);
126+
public override string Serialize() => $"{Lower:yyyy-MM-ddTHH:mm:ssZ},{Upper:yyyy-MM-ddTHH:mm:ssZ}";
127+
}
128+
129+
/// <summary>
130+
/// Empty filter value for operators that don't require values.
131+
/// </summary>
132+
public static readonly FilterValue EmptyValue = new Empty();
133+
134+
// Implicit conversion operators for ease of use
135+
public static implicit operator FilterValue(string value) => new StringValue(value);
136+
public static implicit operator FilterValue(int value) => new IntValue(value);
137+
public static implicit operator FilterValue(DateTime value) => new DateTimeValue(value);
138+
public static implicit operator FilterValue(bool value) => new BooleanValue(value);
139+
public static implicit operator FilterValue(string[] values) => new StringArrayValue(values);
140+
public static implicit operator FilterValue(int[] values) => new IntArrayValue(values);
141+
public static implicit operator FilterValue(DateTime[] values) => new DateTimeArrayValue(values);
142+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Kontent.Ai.Delivery.Abstractions.QueryBuilders.Filtering;
2+
3+
/// <summary>
4+
/// Base interface for all content filtering operations.
5+
/// </summary>
6+
public interface IFilter
7+
{
8+
/// <summary>
9+
/// The property path this filter applies to (e.g., "system.type", "elements.title").
10+
/// </summary>
11+
string PropertyPath { get; }
12+
13+
/// <summary>
14+
/// Serializes this filter to the Kontent.ai API query parameter format.
15+
/// </summary>
16+
/// <returns>The serialized filter string.</returns>
17+
string ToQueryParameter();
18+
}

0 commit comments

Comments
 (0)