diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 508a802..854801c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,9 +27,9 @@ jobs:
- uses: actions/setup-dotnet@v5.2.0
with:
dotnet-version: |
- 6.0.x
8.0.x
9.0.x
+ 10.0.x
- run: dotnet restore
- run: dotnet build --configuration Release --no-restore /warnAsError /nologo /clp:NoSummary
diff --git a/Directory.Build.props b/Directory.Build.props
index b9b1049..68b243d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
- netstandard2.0;net8.0
+ netstandard2.0;net8.0;net10.0
latest
enable
true
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 983db85..d6e9ce0 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -2,40 +2,38 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
+
+
-
+
diff --git a/README.md b/README.md
index eb781bf..1e41039 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ and [IServiceCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.e
For use outside of ASP.NET Core, see the
[example in tests](tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/MinimalExampleTests.cs).
-1. Add configuration (see [options](source/VMelnalksnis.PaperlessDotNet.DependencyInjection/PaperlessOptions.cs))
+1. Add configuration (see [options](source/VMelnalksnis.PaperlessDotNet/PaperlessOptions.cs))
```yaml
"Paperless": {
"BaseAddress": "",
@@ -38,7 +38,7 @@ For use outside of ASP.NET Core, see the
## Filtering
Some objects, such as documents, support filtering on various fields.
The filter format slightly differs from the object itself, and can be seen in a respective `Filter` object;
-for example [DocumentFilter](source/VMelnalksnis.PaperlessDotNet/Filters/DocumentFilter.cs) for documents.
+for example [DocumentFilter](source/VMelnalksnis.PaperlessDotNet/Documents/DocumentFilter.cs) for documents.
Filters can be written inline as expressions:
```csharp
var filteredDocuments = await Client.Documents.Get(
diff --git a/global.json b/global.json
index c0ecc45..cf4ab84 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "9.0.203",
+ "version": "10.0.201",
"rollForward": "latestFeature",
"allowPrerelease": false
}
diff --git a/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/PaperlessOptionsValidator.cs b/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/PaperlessOptionsValidator.cs
new file mode 100644
index 0000000..46dfd69
--- /dev/null
+++ b/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/PaperlessOptionsValidator.cs
@@ -0,0 +1,11 @@
+// Copyright 2022 Valters Melnalksnis
+// Licensed under the Apache License 2.0.
+// See LICENSE file in the project root for full license information.
+
+using Microsoft.Extensions.Options;
+
+namespace VMelnalksnis.PaperlessDotNet.DependencyInjection;
+
+///
+[OptionsValidator]
+public sealed partial class PaperlessOptionsValidator : IValidateOptions;
diff --git a/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/ServiceCollectionExtensions.cs b/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/ServiceCollectionExtensions.cs
index d45f464..22ab580 100644
--- a/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/ServiceCollectionExtensions.cs
+++ b/source/VMelnalksnis.PaperlessDotNet.DependencyInjection/ServiceCollectionExtensions.cs
@@ -3,7 +3,6 @@
// See LICENSE file in the project root for full license information.
using System;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -39,19 +38,14 @@ static ServiceCollectionExtensions()
/// The service collection in which to register the services.
/// A delegate that is used to configure .
/// The for the used by .
-#if NETSTANDARD2_0
- [SuppressMessage("Trimming", "IL2026", Justification = $"{nameof(PaperlessOptions)} contains only system types.")]
-#else
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = $"{nameof(PaperlessOptions)} contains only system types.")]
-#endif
public static IHttpClientBuilder AddPaperlessDotNet(
this IServiceCollection serviceCollection,
Action? config = null)
{
serviceCollection
+ .AddSingleton, PaperlessOptionsValidator>()
.AddOptions()
- .BindConfiguration(PaperlessOptions.Name)
- .ValidateDataAnnotations();
+ .BindConfiguration(PaperlessOptions.Name);
return serviceCollection.AddClient(config);
}
@@ -61,20 +55,15 @@ public static IHttpClientBuilder AddPaperlessDotNet(
/// The configuration to which to bind options models.
/// A delegate that is used to configure .
/// The for the used by .
-#if NETSTANDARD2_0
- [SuppressMessage("Trimming", "IL2026", Justification = $"{nameof(PaperlessOptions)} contains only system types.")]
-#else
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = $"{nameof(PaperlessOptions)} contains only system types.")]
-#endif
public static IHttpClientBuilder AddPaperlessDotNet(
this IServiceCollection serviceCollection,
IConfiguration configuration,
Action? config = null)
{
serviceCollection
+ .AddSingleton, PaperlessOptionsValidator>()
.AddOptions()
- .Bind(configuration.GetSection(PaperlessOptions.Name))
- .ValidateDataAnnotations();
+ .Bind(configuration.GetSection(PaperlessOptions.Name));
return serviceCollection.AddClient(config);
}
diff --git a/source/VMelnalksnis.PaperlessDotNet/Filters/ExpressionExtensions.cs b/source/VMelnalksnis.PaperlessDotNet/Filters/ExpressionExtensions.cs
index b57d24e..af629e1 100644
--- a/source/VMelnalksnis.PaperlessDotNet/Filters/ExpressionExtensions.cs
+++ b/source/VMelnalksnis.PaperlessDotNet/Filters/ExpressionExtensions.cs
@@ -12,6 +12,8 @@
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
+using NodaTime;
+
using VMelnalksnis.PaperlessDotNet.Documents;
using VMelnalksnis.PaperlessDotNet.DocumentTypes;
@@ -94,40 +96,43 @@ private static KeyValuePair ToKeyValuePair(this Expression expre
{
if (expression is BinaryExpression binaryExpression)
{
- var suffix = binaryExpression.GetSuffix();
-
if (binaryExpression.Left is MemberExpression memberExpression)
{
- var value = binaryExpression.Right.Evaluate();
- if (value is bool boolValue)
+ var memberName = memberExpression.GetFilterMemberName();
+
+ var suffix = binaryExpression.GetSuffix();
+ if (suffix is not null)
{
- value = binaryExpression.NodeType is NotEqual
- ? !boolValue
- : boolValue;
+ memberName += $"__{suffix}";
}
- else if (value is null)
+
+ if (binaryExpression.Right.Type == typeof(bool))
{
- value = binaryExpression.NodeType is Equal;
+ var booleanValue = binaryExpression.Right.Evaluate();
+ booleanValue = binaryExpression.NodeType is NotEqual
+ ? !booleanValue
+ : booleanValue;
+
+ return new(memberName, booleanValue ? "true" : "false");
}
- var memberName = memberExpression.GetFilterMemberName();
+ if (binaryExpression.Right.Type == typeof(DateTime))
+ {
+ var dateValue = binaryExpression.Right.Evaluate();
+ var dateString = memberName.Contains("__date")
+ ? dateValue.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
+ : dateValue.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture);
- if (suffix is not null)
+ return new(memberName, dateString);
+ }
+
+ var value = binaryExpression.Right.Evaluate();
+ if (value is null)
{
- memberName += $"__{suffix}";
+ return new(memberName, binaryExpression.NodeType is Equal ? "true" : "false");
}
- return new(
- memberName,
- value switch
- {
- DateTime dateTime when memberName.Contains("__date") => dateTime.ToString(
- "yyyy-MM-dd",
- CultureInfo.InvariantCulture),
- DateTime dateTime => dateTime.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture),
- bool boolean => boolean.ToString().ToLowerInvariant(),
- _ => value.ToString() ?? string.Empty,
- });
+ return new(memberName, value.ToString() ?? string.Empty);
}
}
@@ -144,7 +149,8 @@ DateTime dateTime when memberName.Contains("__date") => dateTime.ToString(
_ => throw new NotImplementedException(),
};
- var value = valueExpression.Evaluate() ?? throw new NotSupportedException("Method calls with null arguments are not supported");
+ var value = valueExpression.Evaluate() ??
+ throw new NotSupportedException("Method calls with null arguments are not supported");
if (value is IEnumerable values)
{
suffix = "in";
@@ -158,7 +164,8 @@ DateTime dateTime when memberName.Contains("__date") => dateTime.ToString(
}
else
{
- var value = methodCallExpression.Arguments[0].Evaluate() ?? throw new InvalidOperationException("Extension method calls on null instances are not supported");
+ var value = methodCallExpression.Arguments[0].Evaluate() ??
+ throw new InvalidOperationException("Extension method calls on null instances are not supported");
if (value is IEnumerable values)
{
suffix = "in";
@@ -232,6 +239,116 @@ private static string GetOrderMemberName(this MemberExpression expression)
private static object? Evaluate(this Expression expression)
{
- return Expression.Lambda(expression).Compile().DynamicInvoke();
+ if (expression.Type == typeof(string))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(Uri))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(int))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(int?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(uint))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(uint?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(float))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(float?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(double))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(double?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(decimal))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(decimal?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(bool))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(bool?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(DateTime))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(DateTime?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(OffsetDate))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(OffsetDate?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(LocalDate))
+ {
+ return expression.Evaluate();
+ }
+
+ if (expression.Type == typeof(LocalDate?))
+ {
+ return expression.Evaluate();
+ }
+
+ if (typeof(IEnumerable).IsAssignableFrom(expression.Type))
+ {
+ return expression.Evaluate>();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(expression.Type), expression.Type, "Unsupported expression type");
+ }
+
+ private static TValue Evaluate(this Expression expression)
+ {
+ return Expression.Lambda>(expression).Compile().Invoke();
}
}
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index f4b9d97..2a20908 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -3,7 +3,7 @@
- net6.0;net8.0;net9.0
+ net8.0;net9.0;net10.0
false
false
false
@@ -23,8 +23,6 @@
-
-
diff --git a/tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/VMelnalksnis.PaperlessDotNet.Tests.Integration.csproj b/tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/VMelnalksnis.PaperlessDotNet.Tests.Integration.csproj
index b9daf2a..e8543f2 100644
--- a/tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/VMelnalksnis.PaperlessDotNet.Tests.Integration.csproj
+++ b/tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/VMelnalksnis.PaperlessDotNet.Tests.Integration.csproj
@@ -18,11 +18,14 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
diff --git a/tests/VMelnalksnis.PaperlessDotNet.Tests/Filters/FilterExpressionTestCases.cs b/tests/VMelnalksnis.PaperlessDotNet.Tests/Filters/FilterExpressionTestCases.cs
index 1d2f960..6b7b5f1 100644
--- a/tests/VMelnalksnis.PaperlessDotNet.Tests/Filters/FilterExpressionTestCases.cs
+++ b/tests/VMelnalksnis.PaperlessDotNet.Tests/Filters/FilterExpressionTestCases.cs
@@ -38,9 +38,9 @@ public FilterExpressionTestCases()
Add(filter => filter.Correspondent!.Name.Contains("foo"), null, "correspondent__name__icontains=foo");
Add(filter => filter.Correspondent!.Name == "foo", null, "correspondent__name__iexact=foo");
- Add(filter => _ids.Contains(filter.Correspondent!.Id), null, "correspondent__id__in=5,23");
+ Add(filter => _ids.AsEnumerable().Contains(filter.Correspondent!.Id), null, "correspondent__id__in=5,23");
Add(filter => new List { 1 }.Contains(filter.Correspondent!.Id), null, "correspondent__id__in=1");
- Add(filter => new[] { 1, 2 }.Contains(filter.Correspondent!.Id), null, "correspondent__id__in=1,2");
+ Add(filter => new[] { 1, 2 }.AsEnumerable().Contains(filter.Correspondent!.Id), null, "correspondent__id__in=1,2");
Add(filter => filter.Added.Date <= date, null, "added__date__lte=2025-04-23");
Add(filter => filter.Added <= date, null, "added__lte=2025-04-23T12%3A23%3A45");