From 658e841872b672484fbf716224fada0c3889e585 Mon Sep 17 00:00:00 2001 From: DrSkillIssue Date: Fri, 21 Aug 2026 16:34:30 -0500 Subject: [PATCH] feat: reject invalid pagination request parameters The AspNetCore registry entry points (Keyset(registry, request) and the PaginateAsync overloads) throw BadHttpRequestException for an unknown sortBy or an unsupported sortDir, and PaginateAsync converts invalid cursors and non-positive page sizes to BadHttpRequestException. ArgumentNullException passes through as a developer error. PaginationSortRegistry.TryResolve returns false for an unsupported direction; Resolve keeps its fallback. Remove unreachable null guards, inline single-caller private methods, update packages, and bump the version to 3.1.0. --- Directory.Build.props | 4 +- Directory.Packages.props | 14 +- docs/api-reference.md | 12 +- docs/patterns.md | 2 +- docs/prebuilt-definitions.md | 2 +- samples/packages.lock.json | 86 ++--- src/EFPagination.Analyzers/packages.lock.json | 95 +++-- .../KeysetBuilderAspNetExtensions.cs | 3 +- .../PaginationEndpointExtensions.cs | 68 ++-- .../PaginationSortRegistryAspNetExtensions.cs | 37 ++ .../packages.lock.json | 46 +-- src/EFPagination/Cursor/CursorDecoder.cs | 170 ++++----- src/EFPagination/Cursor/CursorEncoder.cs | 35 +- .../Definition/PaginationQueryDefinition.cs | 22 +- .../Internal/KeysetProjectionExecutor.cs | 46 +-- src/EFPagination/PaginationExecutor.cs | 1 - src/EFPagination/PaginationExtensions.cs | 62 ++- src/EFPagination/PaginationStreaming.cs | 2 - .../Sorting/PaginationSortRegistry.cs | 13 +- src/EFPagination/packages.lock.json | 106 +++--- test/EFPagination.Tests/packages.lock.json | 353 +++++++++--------- 21 files changed, 580 insertions(+), 599 deletions(-) create mode 100644 src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs diff --git a/Directory.Build.props b/Directory.Build.props index 68e77e8..f321b18 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,11 +18,11 @@ - 10.0.3 + 10.0.11 - 3.0.3 + 3.1.0 DrSkillIssue MIT https://github.com/DrSkillIssue/EFPagination diff --git a/Directory.Packages.props b/Directory.Packages.props index e59f08e..5b2299d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,12 +11,12 @@ - + - + @@ -26,13 +26,13 @@ - - - - + + + + - + diff --git a/docs/api-reference.md b/docs/api-reference.md index 3565556..711b736 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -146,10 +146,8 @@ public sealed class PaginationSortRegistry Behavior: -- `Resolve` uses the default definition when `sortBy` is empty or unknown. -- `TryResolve` returns `false` when `sortBy` does not match any registered field (empty `sortBy` returns the default and `true`). -- Selects the descending variant only when `sortDir` equals `"desc"` (case-insensitive). -- Uses ascending for all other direction values. +- `Resolve` uses the default definition when `sortBy` is empty or unknown, selects the descending variant when `sortDir` equals `"desc"` (case-insensitive), and uses ascending for all other direction values. +- `TryResolve` returns `false` when `sortBy` does not match any registered field or `sortDir` is outside empty, `"asc"`, or `"desc"` (case-insensitive). Empty `sortBy` returns the default and `true`. ## SortField\ / SortField @@ -536,7 +534,7 @@ KeysetQueryBuilder FromRequest( ### Keyset (Registry + Request) -Creates a builder from a sort registry and pagination request, resolving the definition from the request's sort parameters and applying cursors: +Creates a builder from a sort registry and pagination request, resolving the definition from the request's sort parameters and applying cursors. Throws `BadHttpRequestException` for an unknown sort field or an unsupported direction: ```cs KeysetQueryBuilder Keyset( @@ -597,7 +595,7 @@ Task> PaginateAsync( CancellationToken ct = default) where T : class; ``` -Both have a `PaginationSortRegistry` overload that resolves the definition from `request.SortBy`/`request.SortDir`: +Both have a `PaginationSortRegistry` overload that resolves the definition from `request.SortBy`/`request.SortDir`, throwing `BadHttpRequestException` for an unknown sort field or an unsupported direction: ```cs Task> PaginateAsync( @@ -621,6 +619,8 @@ Task> PaginateAsync( The `Expression>` overloads run the projection inside the SQL statement that applies the keyset `ORDER BY`. Subqueries inside the selector stay server-side and the SELECT list materializes only the projected columns plus the keyset key columns. The pagination definition must have 1–8 key columns; outside that range, the overload throws `NotSupportedException`. +Every `PaginateAsync` overload throws `BadHttpRequestException` (status 400) for an invalid or expired cursor and for a `request.PageSize` of zero or less. ASP.NET Core's exception handler middleware maps its status code to the response. + See [Server-Side Projection](patterns.md#server-side-projection) for end-to-end examples. ## Exceptions diff --git a/docs/patterns.md b/docs/patterns.md index f47c342..3eec31e 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -242,7 +242,7 @@ app.MapGet("/api/users", async ( }); ``` -The `Keyset(registry, request)` overload resolves the definition from `request.SortBy`/`request.SortDir`, applies the cursor from `request.After` or `request.Before`, and embeds the sort key in cursor tokens so sort context is preserved across pages. +The `Keyset(registry, request)` overload resolves the definition from `request.SortBy`/`request.SortDir`, applies the cursor from `request.After` or `request.Before`, and embeds the sort key in cursor tokens so sort context is preserved across pages. It throws `BadHttpRequestException` (status 400) for an unknown sort field or an unsupported direction. ## Manual Cursor Encode/Decode diff --git a/docs/prebuilt-definitions.md b/docs/prebuilt-definitions.md index 39c1be2..0a720a5 100644 --- a/docs/prebuilt-definitions.md +++ b/docs/prebuilt-definitions.md @@ -80,7 +80,7 @@ var page = await dbContext.Users `Resolve` is case-insensitive for both field names and `desc`, and falls back to the default definition when the request asks for an unknown sort. -`TryResolve` returns `false` instead of falling back, allowing you to reject invalid sort field names: +`TryResolve` returns `false` for an unknown sort field or an unsupported direction, allowing you to reject invalid sort requests: ```cs if (!Sorts.TryResolve(sortBy, sortDir, out var definition)) diff --git a/samples/packages.lock.json b/samples/packages.lock.json index 97365fd..65075f9 100644 --- a/samples/packages.lock.json +++ b/samples/packages.lock.json @@ -4,105 +4,105 @@ "net10.0": { "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "jc7iVrhQyInR3loraMESfEFaFOtQOB1mRKHjX6QYC9o7YDbfMNbAPnIwlpffnFwhXd6/27FKaaV+sWSoLd4F1g==", "dependencies": { - "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.12", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "resolved": "10.0.11", + "contentHash": "hubA20AGenQ4Sx0ElWaPpB8DISjXpdx463+1zOGRslsT0e/t/06ITv+pHsop8CcJ0d8PZLfgnT7juCDVD79Dkw==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.EntityFrameworkCore.Relational": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "resolved": "10.0.11", + "contentHash": "grznnTJgEYxaWpdKAsTzg6j+89jHgCXWYp+QGtlX5O92+w/VuhWM6JLPYb+uw8M9VhGUvOTsO76dYOy9vNPd5Q==", "dependencies": { - "Microsoft.EntityFrameworkCore": "10.0.3" + "Microsoft.EntityFrameworkCore": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "resolved": "10.0.11", + "contentHash": "ywTQKt32xnVhCzjEQAqFufpEyXkOUfvW/EC/s4xnS8Xaor2xXE+TMUyzhgACqXtZEU5IR95y94RDzHto55Fx7w==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "10.0.3", - "Microsoft.EntityFrameworkCore.Relational": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.Data.Sqlite.Core": "10.0.11", + "Microsoft.EntityFrameworkCore.Relational": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==" + "resolved": "10.0.11", + "contentHash": "PJPtFYsZ+r+uz9qqXWUTEyKeJ1EiBGIJtqavkg9ZXijjGSFAk4Fgi5sqIxj+uAyLZwEKgexDUQXhWhvU6l3+og==" }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "resolved": "2.1.12", + "contentHash": "mAgscpQMLw5/nfA1Q5oJVAT29yROUo1ifZGbbTpx/lwZpSxMUGoYbKfmvdm8oXER+RzxqBmmQzeBEVKfeHv2nw==", "dependencies": { - "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", - "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + "SQLitePCLRaw.lib.e_sqlite3": "2.1.12", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.12" } }, "SQLitePCLRaw.core": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==" + "resolved": "2.1.12", + "contentHash": "ETpNw9DY3ckWLgRRAeCHj+GKOuPi61aeczkXhgHexUvqoZBAYg8RYESE2J7O1M7+o6QbdSEZwrw9bfqztUVWXg==" }, "SQLitePCLRaw.lib.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==" + "resolved": "2.1.12", + "contentHash": "fWi8Dbknuhgg72fWinIdjXVaqO1hHL4YBBwVLnr7e1c9TAZwJ0QE38j9syW1hwx6HaqEVTwI+O07WPdZn8Rp0w==" }, "SQLitePCLRaw.provider.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "resolved": "2.1.12", + "contentHash": "W3oH4XIfCzFrgUSDKHhN6N+dgzA5YHOR2VxX8GB6Qy7CyrJJgxPEG8NirgYWlPQC5P2jz2knSsexWu4tDUL33g==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "efpagination": { "type": "Project", "dependencies": { - "Microsoft.EntityFrameworkCore": "[10.0.3, )" + "Microsoft.EntityFrameworkCore": "[10.0.11, )" } }, "efpagination.aspnetcore": { "type": "Project", "dependencies": { - "EFPagination": "[1.1.3, )" + "EFPagination": "[3.0.3, )" } } } diff --git a/src/EFPagination.Analyzers/packages.lock.json b/src/EFPagination.Analyzers/packages.lock.json index 740c7be..105e768 100644 --- a/src/EFPagination.Analyzers/packages.lock.json +++ b/src/EFPagination.Analyzers/packages.lock.json @@ -4,31 +4,31 @@ ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", + "requested": "[5.6.0, )", + "resolved": "5.6.0", + "contentHash": "r1DrKQ/L0xTw03wJrLr36AMQNslyaeEKBFyFmQcOKa8HX3YvmhY//JEUafb6IR/m0gmaUVCfBTWitKJRNb7YAA==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "Microsoft.CodeAnalysis.Common": "[5.3.0]", - "System.Buffers": "4.6.0", - "System.Collections.Immutable": "9.0.0", - "System.Memory": "4.6.0", - "System.Numerics.Vectors": "4.6.0", - "System.Reflection.Metadata": "9.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.1.0", + "Microsoft.CodeAnalysis.Analyzers": "5.3.0", + "Microsoft.CodeAnalysis.Common": "[5.6.0]", + "System.Buffers": "4.6.1", + "System.Collections.Immutable": "10.0.1", + "System.Memory": "4.6.3", + "System.Numerics.Vectors": "4.6.1", + "System.Reflection.Metadata": "10.0.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", "System.Text.Encoding.CodePages": "8.0.0", - "System.Threading.Tasks.Extensions": "4.6.0" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[10.0.201, )", - "resolved": "10.0.201", - "contentHash": "qxYAmO4ktzd9L+HMdnqWucxpu7bI9undPyACXOMqPyhaiMtbpbYL/n0ACyWIJlbyEJrXFwxiOaBOSasLtDvsCg==", + "requested": "[10.0.400, )", + "resolved": "10.0.400", + "contentHash": "N1Ut0+EmMb2Tma9spHxAgpCspkln4gst1RWYc1PYPn8LA4KJWDSiggKpE183lfR98TNG2C+XnJ71zSYtuCSsqA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "10.0.201", - "Microsoft.SourceLink.Common": "10.0.201", - "System.IO.Hashing": "10.0.5" + "Microsoft.Build.Tasks.Git": "10.0.400", + "Microsoft.SourceLink.Common": "10.0.400", + "System.IO.Hashing": "10.0.11" } }, "NETStandard.Library": { @@ -42,31 +42,31 @@ }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "DMYBnrFZvLnBKn14VavEuuIr31CY6YY2i2L9P8DorS/Qp6ifRR8ZPLdJCFLFfjikNq8DykbYyLd/RP6lSqHcWw==", + "resolved": "10.0.400", + "contentHash": "uXJ/eBDqAo9HlyPjvvvynjHb59KXQWG9txpBzyI1drO6MV3Lhndt2yhxuMKvIga4kND2UKc3xCNXlXBhWU8Hlg==", "dependencies": { - "System.IO.Hashing": "10.0.5" + "System.IO.Hashing": "10.0.11" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", - "resolved": "5.3.0-2.25625.1", - "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" + "resolved": "5.3.0", + "contentHash": "KuLhbZwB0L8JikL86AE5VWEp3RLNjIcp+j8yz9EJ/UBgRz4+qDEjHg/tluRFbpYpD/e37BqaaNFbQ0vqawBwWQ==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "5.3.0", - "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", + "resolved": "5.6.0", + "contentHash": "eWYNB5e92PSdkQ0xcmy2aLtrvBXNydnVi0Hj/VjaAely6XBqA3By+ClGAJaj4d16pzQmrXPLLK9RDVuS1Ec9xQ==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "System.Buffers": "4.6.0", - "System.Collections.Immutable": "9.0.0", - "System.Memory": "4.6.0", - "System.Numerics.Vectors": "4.6.0", - "System.Reflection.Metadata": "9.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.1.0", + "Microsoft.CodeAnalysis.Analyzers": "5.3.0", + "System.Buffers": "4.6.1", + "System.Collections.Immutable": "10.0.1", + "System.Memory": "4.6.3", + "System.Numerics.Vectors": "4.6.1", + "System.Reflection.Metadata": "10.0.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", "System.Text.Encoding.CodePages": "8.0.0", - "System.Threading.Tasks.Extensions": "4.6.0" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "Microsoft.NETCore.Platforms": { @@ -76,8 +76,8 @@ }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "QbBYhkjgL6rCnBfDbzsAJLlsad13TlBHqYCFDIw56OO2g6ix+9RsmY8uxiQGdWwFKbZXaXyAA6jDCzFYVGCZDw==" + "resolved": "10.0.400", + "contentHash": "Knli3Y2k/rdBqUlQ8TP+Sbd0J0jV/0kvgLsxieowoVayioZ0JGfqb9go+/dQJqtU53i6IpZzRxAPxbBT5UJBNg==" }, "System.Buffers": { "type": "Transitive", @@ -86,17 +86,17 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", + "resolved": "10.0.1", + "contentHash": "kdTe61B8P7i2M1pODC3MLbZ/CfFGjpC6c6jzxjQoB5DHZNewayCRqgFUmx3JKB6vLQtozpMQEiw+R5fO32Jv4g==", "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "8IBJWcCT9+e4Bmevm4T7+fQEiAh133KGiz4oiVTgJckd3Q76OFdR1falgn9lpz7+C4HJvogCDJeAa2QmvbeVtg==", + "resolved": "10.0.11", + "contentHash": "OzKDcIRkeNJeC8qAsbn8yJXnfTLP1dtkWILe+T56Gf/z+IkAASi7sMqLqJQat08j5z/mRN5xVtoAwbkMNMoBUQ==", "dependencies": { "System.Buffers": "4.6.1", "System.Memory": "4.6.3" @@ -119,11 +119,10 @@ }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", + "resolved": "10.0.1", + "contentHash": "zpcfT/wacPPhE17zcudozlxQtWN/84qyiMyZNGLnK4cj2IMBtLsZYwYjVnALUhPliwyUVj/P7kaZvBWYBCnf2Q==", "dependencies": { - "System.Collections.Immutable": "9.0.0", - "System.Memory": "4.5.5" + "System.Collections.Immutable": "10.0.1" } }, "System.Runtime.CompilerServices.Unsafe": { @@ -142,10 +141,10 @@ }, "System.Threading.Tasks.Extensions": { "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.1.0" + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } } } diff --git a/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs b/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs index b511adf..495cc1d 100644 --- a/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs +++ b/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs @@ -42,9 +42,8 @@ public static KeysetQueryBuilder Keyset( PaginationRequest request) where T : class { ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(registry); - var definition = registry.Resolve(request.SortBy.AsSpan(), request.SortDir.AsSpan()); + var definition = registry.Resolve(request); return new KeysetQueryBuilder(source, definition) .WithSortBy(request.SortBy) .FromRequest(request); diff --git a/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs b/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs index 281f6f4..ec53042 100644 --- a/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs +++ b/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using Microsoft.AspNetCore.Http; namespace EFPagination.AspNetCore; @@ -31,20 +32,29 @@ public static async Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { - var (cursor, direction) = ResolveCursorAndDirection(request); + var (cursor, direction) = request.Before is not null + ? (request.Before, PaginationDirection.Backward) + : (request.After, PaginationDirection.Forward); - var page = await PaginationExecutor.ExecuteFromCursorAsync( - query, - definition, - new ExecutionOptions( - PageSize: request.PageSize, - Direction: direction, - IncludeCount: includeCount, - MaxPageSize: maxPageSize), - cursor, - ct).ConfigureAwait(false); + try + { + var page = await PaginationExecutor.ExecuteFromCursorAsync( + query, + definition, + new ExecutionOptions( + PageSize: request.PageSize, + Direction: direction, + IncludeCount: includeCount, + MaxPageSize: maxPageSize), + cursor, + ct).ConfigureAwait(false); - return page.ToPaginatedResponse(selector); + return page.ToPaginatedResponse(selector); + } + catch (ArgumentException e) when (e is not ArgumentNullException) + { + throw new BadHttpRequestException(e.Message, e); + } } /// @@ -73,8 +83,7 @@ public static Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { - ArgumentNullException.ThrowIfNull(registry); - var definition = registry.Resolve(request.SortBy.AsSpan(), request.SortDir.AsSpan()); + var definition = registry.Resolve(request); return PaginateAsync(query, definition, request, selector, maxPageSize, includeCount, ct); } @@ -111,12 +120,20 @@ public static async Task> PaginateAsync( ArgumentNullException.ThrowIfNull(selector); var effectivePageSize = request.PageSize > maxPageSize ? maxPageSize : request.PageSize; - var page = await query.Keyset(definition).FromRequest(request) - .MaxPageSize(maxPageSize) - .ConfigureIncludeCount(includeCount) - .TakeAsync(effectivePageSize, selector, ct).ConfigureAwait(false); + var builder = query.Keyset(definition).FromRequest(request).MaxPageSize(maxPageSize); + if (includeCount) + builder = builder.IncludeCount(); - return page.ToPaginatedResponse(); + try + { + var page = await builder.TakeAsync(effectivePageSize, selector, ct).ConfigureAwait(false); + + return page.ToPaginatedResponse(); + } + catch (ArgumentException e) when (e is not ArgumentNullException) + { + throw new BadHttpRequestException(e.Message, e); + } } /// @@ -145,19 +162,8 @@ public static Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { - ArgumentNullException.ThrowIfNull(registry); - var definition = registry.Resolve(request.SortBy.AsSpan(), request.SortDir.AsSpan()); + var definition = registry.Resolve(request); return PaginateAsync(query, definition, request, selector, maxPageSize, includeCount, ct); } - private static KeysetQueryBuilder ConfigureIncludeCount(this KeysetQueryBuilder builder, bool includeCount) where T : class - => includeCount ? builder.IncludeCount() : builder; - - private static (string? cursor, PaginationDirection direction) ResolveCursorAndDirection(PaginationRequest request) - { - if (request.Before is not null) - return (request.Before, PaginationDirection.Backward); - - return (request.After, PaginationDirection.Forward); - } } diff --git a/src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs b/src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs new file mode 100644 index 0000000..23d0302 --- /dev/null +++ b/src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; + +namespace EFPagination.AspNetCore; + +/// +/// Extensions for resolving sort parameters against a . +/// +public static class PaginationSortRegistryAspNetExtensions +{ + /// + /// Resolves the definition for the request's sort parameters. + /// + /// The entity type handled by the registry. + /// The registry of sortable fields. + /// The bound pagination request parameters. + /// The matched pagination definition, or the default definition when no sort was requested. + /// names an unknown sort field. + /// -or- + /// names an unsupported sort direction. + public static PaginationQueryDefinition Resolve(this PaginationSortRegistry registry, PaginationRequest request) + { + ArgumentNullException.ThrowIfNull(registry); + + if (registry.TryResolve(request.SortBy.AsSpan(), request.SortDir.AsSpan(), out var definition)) + return definition; + + var dir = request.SortDir.AsSpan(); + if (!dir.IsEmpty && + !dir.Equals("asc", StringComparison.OrdinalIgnoreCase) && + !dir.Equals("desc", StringComparison.OrdinalIgnoreCase)) + { + throw new BadHttpRequestException($"sortDir '{request.SortDir}' is not supported. sortDir must be 'asc' or 'desc'."); + } + + throw new BadHttpRequestException($"'{request.SortBy}' is not a sortable field."); + } +} diff --git a/src/EFPagination.AspNetCore/packages.lock.json b/src/EFPagination.AspNetCore/packages.lock.json index 48f50ff..2b67a10 100644 --- a/src/EFPagination.AspNetCore/packages.lock.json +++ b/src/EFPagination.AspNetCore/packages.lock.json @@ -4,57 +4,57 @@ "net10.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[10.0.201, )", - "resolved": "10.0.201", - "contentHash": "qxYAmO4ktzd9L+HMdnqWucxpu7bI9undPyACXOMqPyhaiMtbpbYL/n0ACyWIJlbyEJrXFwxiOaBOSasLtDvsCg==", + "requested": "[10.0.400, )", + "resolved": "10.0.400", + "contentHash": "N1Ut0+EmMb2Tma9spHxAgpCspkln4gst1RWYc1PYPn8LA4KJWDSiggKpE183lfR98TNG2C+XnJ71zSYtuCSsqA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "10.0.201", - "Microsoft.SourceLink.Common": "10.0.201", - "System.IO.Hashing": "10.0.5" + "Microsoft.Build.Tasks.Git": "10.0.400", + "Microsoft.SourceLink.Common": "10.0.400", + "System.IO.Hashing": "10.0.11" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "DMYBnrFZvLnBKn14VavEuuIr31CY6YY2i2L9P8DorS/Qp6ifRR8ZPLdJCFLFfjikNq8DykbYyLd/RP6lSqHcWw==", + "resolved": "10.0.400", + "contentHash": "uXJ/eBDqAo9HlyPjvvvynjHb59KXQWG9txpBzyI1drO6MV3Lhndt2yhxuMKvIga4kND2UKc3xCNXlXBhWU8Hlg==", "dependencies": { - "System.IO.Hashing": "10.0.5" + "System.IO.Hashing": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "QbBYhkjgL6rCnBfDbzsAJLlsad13TlBHqYCFDIw56OO2g6ix+9RsmY8uxiQGdWwFKbZXaXyAA6jDCzFYVGCZDw==" + "resolved": "10.0.400", + "contentHash": "Knli3Y2k/rdBqUlQ8TP+Sbd0J0jV/0kvgLsxieowoVayioZ0JGfqb9go+/dQJqtU53i6IpZzRxAPxbBT5UJBNg==" }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "8IBJWcCT9+e4Bmevm4T7+fQEiAh133KGiz4oiVTgJckd3Q76OFdR1falgn9lpz7+C4HJvogCDJeAa2QmvbeVtg==" + "resolved": "10.0.11", + "contentHash": "OzKDcIRkeNJeC8qAsbn8yJXnfTLP1dtkWILe+T56Gf/z+IkAASi7sMqLqJQat08j5z/mRN5xVtoAwbkMNMoBUQ==" }, "efpagination": { "type": "Project", "dependencies": { - "Microsoft.EntityFrameworkCore": "[10.0.3, )" + "Microsoft.EntityFrameworkCore": "[10.0.11, )" } }, "Microsoft.EntityFrameworkCore": { "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11" } } } diff --git a/src/EFPagination/Cursor/CursorDecoder.cs b/src/EFPagination/Cursor/CursorDecoder.cs index b8feb08..0dba568 100644 --- a/src/EFPagination/Cursor/CursorDecoder.cs +++ b/src/EFPagination/Cursor/CursorDecoder.cs @@ -47,126 +47,92 @@ public static bool TryDecodeWithDefinition( return false; var payload = scratch[..bytesWritten]; - if (!ParseHeader(payload, signingKey, out var header)) + if (payload.Length < 2 + 4 || payload[0] != CursorFormat.Version) return false; - if (header.Fingerprint != definition.SchemaFingerprint) - return false; - - var reader = new CursorReader(payload.Slice(header.BodyStart, header.BodyEnd - header.BodyStart)); - if (!DecodeBody(ref reader, definition, bindings, out var valueCount)) - return false; - if (reader.Failed || reader.Remaining != 0) - return false; + var flags = payload[1]; + var signed = (flags & CursorFormat.FlagSigned) != 0; - metadata = new CursorMetadata(header.SortBy, header.TotalCount, header.Fingerprint, valueCount); - return true; - } - catch (FormatException) { return false; } - catch (InvalidOperationException) { return false; } - catch (InvalidCastException) { return false; } - finally - { - if (rented is not null) ArrayPool.Shared.Return(rented); - } - } + int bodyEnd; + if (signed) + { + if (signingKey is null) return false; + if (payload.Length < 2 + 4 + CursorFormat.HmacTruncatedLength) return false; + bodyEnd = payload.Length - CursorFormat.HmacTruncatedLength; + + Span expected = stackalloc byte[HMACSHA256.HashSizeInBytes]; + HMACSHA256.HashData(signingKey, payload[..bodyEnd], expected); + if (!CryptographicOperations.FixedTimeEquals(expected[..CursorFormat.HmacTruncatedLength], payload.Slice(bodyEnd, CursorFormat.HmacTruncatedLength))) + return false; + } + else + { + if (signingKey is not null) return false; + bodyEnd = payload.Length; + } - private readonly ref struct ParsedHeader - { - public uint Fingerprint { get; init; } - public string? SortBy { get; init; } - public int? TotalCount { get; init; } - public int BodyStart { get; init; } - public int BodyEnd { get; init; } - } + var headerReader = new CursorReader(payload[2..bodyEnd]); - [SkipLocalsInit] - private static bool ParseHeader(ReadOnlySpan payload, byte[]? signingKey, out ParsedHeader header) - { - header = default; - if (payload.Length < 2 + 4 || payload[0] != CursorFormat.Version) - return false; + var fingerprint = headerReader.ReadUInt32(); + if (headerReader.Failed) return false; - var flags = payload[1]; - var signed = (flags & CursorFormat.FlagSigned) != 0; + string? sortBy = null; + if ((flags & CursorFormat.FlagSortBy) != 0) + { + sortBy = headerReader.ReadString(); + if (headerReader.Failed) return false; + } - int bodyEnd; - if (signed) - { - if (signingKey is null) return false; - if (payload.Length < 2 + 4 + CursorFormat.HmacTruncatedLength) return false; - bodyEnd = payload.Length - CursorFormat.HmacTruncatedLength; + int? totalCount = null; + if ((flags & CursorFormat.FlagTotalCount) != 0) + { + totalCount = headerReader.ReadVarInt32(); + if (headerReader.Failed) return false; + } - Span expected = stackalloc byte[HMACSHA256.HashSizeInBytes]; - HMACSHA256.HashData(signingKey, payload[..bodyEnd], expected); - if (!CryptographicOperations.FixedTimeEquals(expected[..CursorFormat.HmacTruncatedLength], payload.Slice(bodyEnd, CursorFormat.HmacTruncatedLength))) + if (fingerprint != definition.SchemaFingerprint) return false; - } - else - { - if (signingKey is not null) return false; - bodyEnd = payload.Length; - } - var reader = new CursorReader(payload[2..bodyEnd]); + var bodyStart = 2 + headerReader.Position; + var reader = new CursorReader(payload.Slice(bodyStart, bodyEnd - bodyStart)); - var fingerprint = reader.ReadUInt32(); - if (reader.Failed) return false; - - string? sortBy = null; - if ((flags & CursorFormat.FlagSortBy) != 0) - { - sortBy = reader.ReadString(); + var count = (int)reader.ReadVarUInt32(); if (reader.Failed) return false; - } - int? totalCount = null; - if ((flags & CursorFormat.FlagTotalCount) != 0) - { - totalCount = reader.ReadVarInt32(); - if (reader.Failed) return false; - } + var columns = definition.Columns; + if (count != columns.Length || bindings.Length < count) return false; - header = new ParsedHeader - { - Fingerprint = fingerprint, - SortBy = sortBy, - TotalCount = totalCount, - BodyStart = 2 + reader.Position, - BodyEnd = bodyEnd, - }; - return true; - } - - private static bool DecodeBody(ref CursorReader reader, PaginationQueryDefinition definition, ColumnBinding[] bindings, out int valueCount) - { - valueCount = 0; - var count = (int)reader.ReadVarUInt32(); - if (reader.Failed) return false; + var nullMaskLength = (count + 7) >>> 3; + var nullMask = reader.ReadRawBytes(nullMaskLength); + if (reader.Failed) return false; - var columns = definition.Columns; - if (count != columns.Length || bindings.Length < count) return false; + for (var i = 0; i < count; i++) + { + var col = columns[i]; + if (((nullMask[i >>> 3] >>> (i & 7)) & 1) != 0) + { + if (!col.IsNullable) return false; + col.WriteBindingFromBoxed(null, bindings[i]); + } + else + { + col.DecodeCursorValueInto(ref reader, bindings[i]); + if (reader.Failed) return false; + } + } - var nullMaskLength = (count + 7) >>> 3; - var nullMask = reader.ReadRawBytes(nullMaskLength); - if (reader.Failed) return false; + if (reader.Failed || reader.Remaining != 0) + return false; - for (var i = 0; i < count; i++) + metadata = new CursorMetadata(sortBy, totalCount, fingerprint, count); + return true; + } + catch (FormatException) { return false; } + catch (InvalidOperationException) { return false; } + catch (InvalidCastException) { return false; } + finally { - var col = columns[i]; - if (((nullMask[i >>> 3] >>> (i & 7)) & 1) != 0) - { - if (!col.IsNullable) return false; - col.WriteBindingFromBoxed(null, bindings[i]); - } - else - { - col.DecodeCursorValueInto(ref reader, bindings[i]); - if (reader.Failed) return false; - } + if (rented is not null) ArrayPool.Shared.Return(rented); } - - valueCount = count; - return true; } } diff --git a/src/EFPagination/Cursor/CursorEncoder.cs b/src/EFPagination/Cursor/CursorEncoder.cs index 39015ba..66af74b 100644 --- a/src/EFPagination/Cursor/CursorEncoder.cs +++ b/src/EFPagination/Cursor/CursorEncoder.cs @@ -50,8 +50,21 @@ private static string EncodeCore(PaginationCursorOptions options, scoped { var writer = new CursorWriter(buffer); body.Write(ref writer); - AppendHmac(buffer, options.SigningKey); - return Base64UrlEncode(buffer); + + if (options.SigningKey is byte[] signingKey) + { + Span hmacFull = stackalloc byte[HMACSHA256.HashSizeInBytes]; + HMACSHA256.HashData(signingKey, buffer.WrittenSpan, hmacFull); + var dest = buffer.GetSpan(CursorFormat.HmacTruncatedLength); + hmacFull[..CursorFormat.HmacTruncatedLength].CopyTo(dest); + buffer.Advance(CursorFormat.HmacTruncatedLength); + } + + var len = Base64Url.GetEncodedLength(buffer.Written); + return string.Create(len, buffer, static (span, state) => + { + Base64Url.TryEncodeToChars(state.WrittenSpan, span, out _); + }); } finally { @@ -128,22 +141,4 @@ private static void WriteHeader(ref CursorWriter writer, PaginationCursorOptions writer.WriteVarUInt32((uint)valueCount); } - private static void AppendHmac(CursorBuffer buffer, byte[]? signingKey) - { - if (signingKey is null) return; - Span hmacFull = stackalloc byte[HMACSHA256.HashSizeInBytes]; - HMACSHA256.HashData(signingKey, buffer.WrittenSpan, hmacFull); - var dest = buffer.GetSpan(CursorFormat.HmacTruncatedLength); - hmacFull[..CursorFormat.HmacTruncatedLength].CopyTo(dest); - buffer.Advance(CursorFormat.HmacTruncatedLength); - } - - private static string Base64UrlEncode(CursorBuffer buffer) - { - var len = Base64Url.GetEncodedLength(buffer.Written); - return string.Create(len, buffer, static (span, state) => - { - Base64Url.TryEncodeToChars(state.WrittenSpan, span, out _); - }); - } } diff --git a/src/EFPagination/Definition/PaginationQueryDefinition.cs b/src/EFPagination/Definition/PaginationQueryDefinition.cs index abad80f..7e4d6c6 100644 --- a/src/EFPagination/Definition/PaginationQueryDefinition.cs +++ b/src/EFPagination/Definition/PaginationQueryDefinition.cs @@ -15,19 +15,7 @@ internal PaginationQueryDefinition( { Columns = columns; PredicateTemplate = FilterPredicateStrategy.CreateTemplate(columns); - SchemaFingerprint = ComputeFingerprint(columns); - } - - internal PaginationColumn[] Columns { get; } - - internal CachedPredicateTemplate PredicateTemplate { get; } - - internal int ColumnCount => Columns.Length; - - internal uint SchemaFingerprint { get; } - private static uint ComputeFingerprint(PaginationColumn[] columns) - { uint hash = 2166136261; for (var i = 0; i < columns.Length; i++) { @@ -38,6 +26,14 @@ private static uint ComputeFingerprint(PaginationColumn[] columns) hash ^= columns[i].IsDescending ? 1u : 0u; hash *= 16777619; } - return hash; + SchemaFingerprint = hash; } + + internal PaginationColumn[] Columns { get; } + + internal CachedPredicateTemplate PredicateTemplate { get; } + + internal int ColumnCount => Columns.Length; + + internal uint SchemaFingerprint { get; } } diff --git a/src/EFPagination/Internal/KeysetProjectionExecutor.cs b/src/EFPagination/Internal/KeysetProjectionExecutor.cs index 14399f2..b9586a3 100644 --- a/src/EFPagination/Internal/KeysetProjectionExecutor.cs +++ b/src/EFPagination/Internal/KeysetProjectionExecutor.cs @@ -47,9 +47,21 @@ public static async Task> ExecuteAsync( .ResolveAsync(builder.ShouldIncludeCount, resolved.TotalCount, builder.Source, ct) .ConfigureAwait(false); - var (next, previous) = EncodeCursorPair( - builder.Definition, page, page.HasMore, resolved.HasInitialReference, - builder.Direction, sortBy, totalCount); + string? next = null; + string? previous = null; + if (page.Count > 0) + { + var options = new PaginationCursorOptions(sortBy, PaginationCount.AsNullable(totalCount)); + + var hasPrevious = builder.Direction == PaginationDirection.Forward ? resolved.HasInitialReference : page.HasMore; + var hasNext = builder.Direction == PaginationDirection.Forward ? page.HasMore : resolved.HasInitialReference; + + if (hasNext) + next = EncodeFromIndex(builder.Definition, page, page.Count - 1, options); + + if (hasPrevious) + previous = EncodeFromIndex(builder.Definition, page, 0, options); + } return new CursorPage(page.ToItemList(), next, previous, totalCount); } @@ -105,34 +117,6 @@ public static async IAsyncEnumerable> StreamAsync( } } - private static (string? Next, string? Previous) EncodeCursorPair( - PaginationQueryDefinition definition, - ProjectionMaterializedPage page, - bool hasMore, - bool hasInitialReference, - PaginationDirection direction, - string? sortBy, - int totalCount) where T : class - { - if (page.Count == 0) return (null, null); - - var options = new PaginationCursorOptions(sortBy, PaginationCount.AsNullable(totalCount)); - - var hasPrevious = direction == PaginationDirection.Forward ? hasInitialReference : hasMore; - var hasNext = direction == PaginationDirection.Forward ? hasMore : hasInitialReference; - - string? next = null; - string? previous = null; - - if (hasNext) - next = EncodeFromIndex(definition, page, page.Count - 1, options); - - if (hasPrevious) - previous = EncodeFromIndex(definition, page, 0, options); - - return (next, previous); - } - private static string EncodeFromIndex( PaginationQueryDefinition definition, ProjectionMaterializedPage page, diff --git a/src/EFPagination/PaginationExecutor.cs b/src/EFPagination/PaginationExecutor.cs index a0ebd26..cbf43ea 100644 --- a/src/EFPagination/PaginationExecutor.cs +++ b/src/EFPagination/PaginationExecutor.cs @@ -185,7 +185,6 @@ private static async Task> ExecuteCoreAsync( PaginationContext context, CancellationToken ct) where T : class { - ArgumentNullException.ThrowIfNull(query); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(options.PageSize); var (items, hasMore, totalCount) = await MaterializeWithCountAsync( diff --git a/src/EFPagination/PaginationExtensions.cs b/src/EFPagination/PaginationExtensions.cs index 639a4cd..999722d 100644 --- a/src/EFPagination/PaginationExtensions.cs +++ b/src/EFPagination/PaginationExtensions.cs @@ -144,7 +144,10 @@ private static PaginationContext PaginateCore( using var activity = PaginationDiagnostics.StartPaginate(columns, direction, predicateTemplate is not null); - var orderedQuery = ApplyOrdering(source, columns, direction); + var orderedQuery = columns[0].ApplyOrderBy(source, direction); + for (var i = 1; i < columns.Length; i++) + orderedQuery = columns[i].ApplyThenOrderBy(orderedQuery, direction); + if (bindings is null || bindings.Length == 0) return new PaginationContext(orderedQuery, orderedQuery, columns, direction, predicateTemplate); @@ -307,8 +310,29 @@ internal static ColumnBinding[] BuildBindingsFromColumnValues( { var bindings = new ColumnBinding[columns.Length]; - if (referenceValues.Length == columns.Length && TryPopulatePositional(columns, referenceValues, bindings)) - return bindings; + if (referenceValues.Length == columns.Length) + { + var positional = true; + for (var i = 0; i < columns.Length; i++) + { + if (!string.Equals(referenceValues[i].Name, columns[i].GetRequiredPropertyNameForColumnValues(), StringComparison.OrdinalIgnoreCase)) + { + positional = false; + break; + } + } + + if (positional) + { + for (var i = 0; i < columns.Length; i++) + { + var binding = columns[i].CreateBinding(); + columns[i].WriteBindingFromBoxed(referenceValues[i].Value, binding); + bindings[i] = binding; + } + return bindings; + } + } for (var i = 0; i < columns.Length; i++) { @@ -336,27 +360,6 @@ internal static ColumnBinding[] BuildBindingsFromColumnValues( return bindings; } - private static bool TryPopulatePositional( - PaginationColumn[] columns, - ReadOnlySpan values, - ColumnBinding[] bindings) - { - for (var i = 0; i < columns.Length; i++) - { - if (!string.Equals(values[i].Name, columns[i].GetRequiredPropertyNameForColumnValues(), StringComparison.OrdinalIgnoreCase)) - return false; - } - - for (var i = 0; i < columns.Length; i++) - { - var binding = columns[i].CreateBinding(); - columns[i].WriteBindingFromBoxed(values[i].Value, binding); - bindings[i] = binding; - } - - return true; - } - private static Expression> BuildFilterPredicateFromBindings( PaginationColumn[] columns, PaginationDirection direction, @@ -372,15 +375,4 @@ private static Expression> BuildFilterPredicateFromBindings( return FastLambda.Create(body, param); } - private static IOrderedQueryable ApplyOrdering( - IQueryable source, - PaginationColumn[] columns, - PaginationDirection direction) - { - var orderedQuery = columns[0].ApplyOrderBy(source, direction); - for (var i = 1; i < columns.Length; i++) - orderedQuery = columns[i].ApplyThenOrderBy(orderedQuery, direction); - - return orderedQuery; - } } diff --git a/src/EFPagination/PaginationStreaming.cs b/src/EFPagination/PaginationStreaming.cs index 19e6f77..ecb7f5f 100644 --- a/src/EFPagination/PaginationStreaming.cs +++ b/src/EFPagination/PaginationStreaming.cs @@ -30,8 +30,6 @@ public static IAsyncEnumerable> PaginateAllAsync( int pageSize, CancellationToken ct = default) where T : class { - ArgumentNullException.ThrowIfNull(query); - ArgumentNullException.ThrowIfNull(definition); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageSize); var context = query.Paginate(definition, PaginationDirection.Forward); diff --git a/src/EFPagination/Sorting/PaginationSortRegistry.cs b/src/EFPagination/Sorting/PaginationSortRegistry.cs index b6b512c..0221486 100644 --- a/src/EFPagination/Sorting/PaginationSortRegistry.cs +++ b/src/EFPagination/Sorting/PaginationSortRegistry.cs @@ -63,23 +63,28 @@ public PaginationQueryDefinition Resolve(ReadOnlySpan sortBy, ReadOnlyS /// Attempts to resolve a pagination definition for the requested sort field and direction. /// /// The requested logical sort field name. - /// The requested direction; desc selects descending. + /// The requested direction: asc, desc, or empty for ascending. /// When this method returns , the matched definition. - /// if a matching field was found or was empty (default used); if the field was not found. + /// if a definition was resolved; otherwise, . public bool TryResolve( ReadOnlySpan sortBy, ReadOnlySpan sortDir, out PaginationQueryDefinition definition) { + var isDesc = sortDir.Equals("desc", StringComparison.OrdinalIgnoreCase); + if (!isDesc && !sortDir.IsEmpty && !sortDir.Equals("asc", StringComparison.OrdinalIgnoreCase)) + { + definition = default!; + return false; + } + if (sortBy.IsEmpty) { definition = _default; return true; } - var isDesc = sortDir.Equals("desc", StringComparison.OrdinalIgnoreCase); var lookup = isDesc ? _descLookup : _ascLookup; - return lookup.TryGetValue(sortBy, out definition!); } } diff --git a/src/EFPagination/packages.lock.json b/src/EFPagination/packages.lock.json index 3426653..175ae71 100644 --- a/src/EFPagination/packages.lock.json +++ b/src/EFPagination/packages.lock.json @@ -4,119 +4,119 @@ "net10.0": { "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[10.0.201, )", - "resolved": "10.0.201", - "contentHash": "qxYAmO4ktzd9L+HMdnqWucxpu7bI9undPyACXOMqPyhaiMtbpbYL/n0ACyWIJlbyEJrXFwxiOaBOSasLtDvsCg==", + "requested": "[10.0.400, )", + "resolved": "10.0.400", + "contentHash": "N1Ut0+EmMb2Tma9spHxAgpCspkln4gst1RWYc1PYPn8LA4KJWDSiggKpE183lfR98TNG2C+XnJ71zSYtuCSsqA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "10.0.201", - "Microsoft.SourceLink.Common": "10.0.201", - "System.IO.Hashing": "10.0.5" + "Microsoft.Build.Tasks.Git": "10.0.400", + "Microsoft.SourceLink.Common": "10.0.400", + "System.IO.Hashing": "10.0.11" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "DMYBnrFZvLnBKn14VavEuuIr31CY6YY2i2L9P8DorS/Qp6ifRR8ZPLdJCFLFfjikNq8DykbYyLd/RP6lSqHcWw==", + "resolved": "10.0.400", + "contentHash": "uXJ/eBDqAo9HlyPjvvvynjHb59KXQWG9txpBzyI1drO6MV3Lhndt2yhxuMKvIga4kND2UKc3xCNXlXBhWU8Hlg==", "dependencies": { - "System.IO.Hashing": "10.0.5" + "System.IO.Hashing": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Caching.Memory": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3" + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "QbBYhkjgL6rCnBfDbzsAJLlsad13TlBHqYCFDIw56OO2g6ix+9RsmY8uxiQGdWwFKbZXaXyAA6jDCzFYVGCZDw==" + "resolved": "10.0.400", + "contentHash": "Knli3Y2k/rdBqUlQ8TP+Sbd0J0jV/0kvgLsxieowoVayioZ0JGfqb9go+/dQJqtU53i6IpZzRxAPxbBT5UJBNg==" }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "8IBJWcCT9+e4Bmevm4T7+fQEiAh133KGiz4oiVTgJckd3Q76OFdR1falgn9lpz7+C4HJvogCDJeAa2QmvbeVtg==" + "resolved": "10.0.11", + "contentHash": "OzKDcIRkeNJeC8qAsbn8yJXnfTLP1dtkWILe+T56Gf/z+IkAASi7sMqLqJQat08j5z/mRN5xVtoAwbkMNMoBUQ==" } } } diff --git a/test/EFPagination.Tests/packages.lock.json b/test/EFPagination.Tests/packages.lock.json index 4ce8473..8bf9a72 100644 --- a/test/EFPagination.Tests/packages.lock.json +++ b/test/EFPagination.Tests/packages.lock.json @@ -4,80 +4,80 @@ "net10.0": { "coverlet.collector": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "EMkj/2F6n6IVPrvGYkqzGJs6phuGGkq6N+E7KW9rNyzNxXbwQ1KfMqWyXNf9nCNEQOA6IjFwmOLvkriwKE7Orw==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" }, "FluentAssertions": { "type": "Direct", - "requested": "[8.8.0, )", - "resolved": "8.8.0", - "contentHash": "m0kwcqBwvVel03FuMa7Ozo/oTaxYbjeNlcOhQFkyQpwX/8wks6RNl/Jnn58DCZVs6c2oG1RsCZw7HfKSaxLm3w==" + "requested": "[8.10.0, )", + "resolved": "8.10.0", + "contentHash": "yGvv+xp0gHFgGwhQcGoDdKIrhLhPi3zYYWWLLHPMctr+G6PWD7QH8L4wqoa5WRZquPbfSV/cP1MraI7ppAHrWw==" }, "GitHubActionsTestLogger": { "type": "Direct", - "requested": "[3.0.1, )", - "resolved": "3.0.1", - "contentHash": "HCfw7V1yuKxnm89vVKrb8b6aug96PUgxN9Z0YthEFCR8JOfTJF4XcU1i6RX2eEB08p3zriuFIG7BGXns4PyE8Q==" + "requested": "[3.0.5, )", + "resolved": "3.0.5", + "contentHash": "LM5IKPSylMYp42YC9bnL+JNUnQUS2y9TgoFLjnqeXLpDvqeyiTVc9fSh6m1KzhgLCKLGtUkpZxk9xI9/PHFaiw==" }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", + "requested": "[5.6.0, )", + "resolved": "5.6.0", + "contentHash": "r1DrKQ/L0xTw03wJrLr36AMQNslyaeEKBFyFmQcOKa8HX3YvmhY//JEUafb6IR/m0gmaUVCfBTWitKJRNb7YAA==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "Microsoft.CodeAnalysis.Common": "[5.3.0]" + "Microsoft.CodeAnalysis.Analyzers": "5.3.0", + "Microsoft.CodeAnalysis.Common": "[5.6.0]" } }, "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "jc7iVrhQyInR3loraMESfEFaFOtQOB1mRKHjX6QYC9o7YDbfMNbAPnIwlpffnFwhXd6/27FKaaV+sWSoLd4F1g==", "dependencies": { - "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.12", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.EntityFrameworkCore.SqlServer": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "q0fUFQ4PG4uhKwP+D5dsvlDQbwNpEfWsag3HaoGzkkKj+OQ90ya9W0HuFtwigwEEqc/H1SzDZYUAv7Ya1hh5ow==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "LClSs5cNN6za2Uk7IeH7RAP4Qe5EMXxFsD9i53ncPx21TVg04IEjoFyxvvsSNkq2/Ux9HyJsJ2qEO8Tl7Gcrrg==", "dependencies": { - "Microsoft.Data.SqlClient": "6.1.1", - "Microsoft.EntityFrameworkCore.Relational": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.Data.SqlClient": "6.1.6", + "Microsoft.EntityFrameworkCore.Relational": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[18.0.1, )", - "resolved": "18.0.1", - "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", + "requested": "[18.9.0, )", + "resolved": "18.9.0", + "contentHash": "xIzVXa/VpKXqDWzyC/5Hw8JbFY5u9k3Jc53CpcjBiOXvkQGio484LD9FNwOiGUSMDcYL3Rcw9sNgGi5WrswlPQ==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.1", - "Microsoft.TestPlatform.TestHost": "18.0.1" + "Microsoft.CodeCoverage": "18.9.0", + "Microsoft.TestPlatform.TestHost": "18.9.0" } }, "Moq": { @@ -102,28 +102,28 @@ }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[3.1.5, )", - "resolved": "3.1.5", - "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + "requested": "[4.0.0, )", + "resolved": "4.0.0", + "contentHash": "kzLFyBYUnoidpGOXvpNOfIo8C92gVgVR6X8ncHwhcrDYugiOut5rrhDkr0L3cWHd7J2pKXf6LgaeXoBBDlx1ag==" }, "Azure.Core": { "type": "Transitive", - "resolved": "1.47.1", - "contentHash": "oPcncSsDHuxB8SC522z47xbp2+ttkcKv2YZ90KXhRKN0YQd2+7l1UURT9EBzUNEXtkLZUOAB5xbByMTrYRh3yA==", + "resolved": "1.50.0", + "contentHash": "GBNKZEhdIbTXxedvD3R7I/yDVFX9jJJEz02kCziFSJxspSQ5RMHc3GktulJ1s7+ffXaXD7kMgrtdQTaggyInLw==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.ClientModel": "1.5.1", + "System.ClientModel": "1.8.0", "System.Memory.Data": "8.0.1" } }, "Azure.Identity": { "type": "Transitive", - "resolved": "1.14.2", - "contentHash": "YhNMwOTwT+I2wIcJKSdP0ADyB2aK+JaYWZxO8LSRDm5w77LFr0ykR9xmt2ZV5T1gaI7xU6iNFIh/yW1dAlpddQ==", + "resolved": "1.17.1", + "contentHash": "MSZkBrctcpiGxs9Cvr2VKKoN6qFLZlP3I6xuCWJ9iTgitI5Rgxtk5gfOSpXPZE3+CJmZ/mnqpQyGyjawFn5Vvg==", "dependencies": { - "Azure.Core": "1.46.1", - "Microsoft.Identity.Client": "4.73.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.73.1" + "Azure.Core": "1.50.0", + "Microsoft.Identity.Client": "4.78.0", + "Microsoft.Identity.Client.Extensions.Msal": "4.78.0" } }, "Castle.Core": { @@ -139,44 +139,41 @@ "resolved": "8.0.0", "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==" }, - "Microsoft.Bcl.Cryptography": { - "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "YgZYAWzyNuPVtPq6WNm0bqOWNjYaWgl5mBWTGZyNoXitYBUYSp6iUB9AwK0V1mo793qRJUXz2t6UZrWITZSvuQ==" - }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", - "resolved": "5.3.0-2.25625.1", - "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" + "resolved": "5.3.0", + "contentHash": "KuLhbZwB0L8JikL86AE5VWEp3RLNjIcp+j8yz9EJ/UBgRz4+qDEjHg/tluRFbpYpD/e37BqaaNFbQ0vqawBwWQ==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "5.3.0", - "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", + "resolved": "5.6.0", + "contentHash": "eWYNB5e92PSdkQ0xcmy2aLtrvBXNydnVi0Hj/VjaAely6XBqA3By+ClGAJaj4d16pzQmrXPLLK9RDVuS1Ec9xQ==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" + "Microsoft.CodeAnalysis.Analyzers": "5.3.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" + "resolved": "18.9.0", + "contentHash": "MtegCIKGuG0r/LCdIjoR1HgzCGIUBhR3aNdbtQpts5vMXQuqBDZ2Jsd2uFKoHFM2XrQV6ZrDf3F5oLi3SLTp8w==" }, "Microsoft.Data.SqlClient": { "type": "Transitive", - "resolved": "6.1.1", - "contentHash": "syGQmIUPAYYHAHyTD8FCkTNThpQWvoA7crnIQRMfp8dyB5A2cWU3fQexlRTFkVmV7S0TjVmthi0LJEFVjHo8AQ==", + "resolved": "6.1.6", + "contentHash": "6abRPXrjjEWxcNkTomBIzBdGolYnwD0ykP+6YAynaEEfKlGe94ZSQd58pggucusp28xUA7wLo9XcvzU92ZuF6Q==", "dependencies": { - "Azure.Core": "1.47.1", - "Azure.Identity": "1.14.2", - "Microsoft.Bcl.Cryptography": "9.0.4", + "Azure.Core": "1.50.0", + "Azure.Identity": "1.17.1", "Microsoft.Data.SqlClient.SNI.runtime": "6.0.2", - "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.11", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Broker": "4.84.2", "Microsoft.IdentityModel.JsonWebTokens": "7.7.1", "Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.7.1", "Microsoft.SqlServer.Server": "1.0.0", - "System.Configuration.ConfigurationManager": "9.0.4", - "System.Security.Cryptography.Pkcs": "9.0.4" + "System.Configuration.ConfigurationManager": "9.0.11", + "System.IdentityModel.Tokens.Jwt": "7.7.1", + "System.Security.Cryptography.Pkcs": "9.0.11" } }, "Microsoft.Data.SqlClient.SNI.runtime": { @@ -186,146 +183,160 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "resolved": "10.0.11", + "contentHash": "hubA20AGenQ4Sx0ElWaPpB8DISjXpdx463+1zOGRslsT0e/t/06ITv+pHsop8CcJ0d8PZLfgnT7juCDVD79Dkw==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.EntityFrameworkCore.Relational": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "resolved": "10.0.11", + "contentHash": "grznnTJgEYxaWpdKAsTzg6j+89jHgCXWYp+QGtlX5O92+w/VuhWM6JLPYb+uw8M9VhGUvOTsO76dYOy9vNPd5Q==", "dependencies": { - "Microsoft.EntityFrameworkCore": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.EntityFrameworkCore": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "resolved": "10.0.11", + "contentHash": "ywTQKt32xnVhCzjEQAqFufpEyXkOUfvW/EC/s4xnS8Xaor2xXE+TMUyzhgACqXtZEU5IR95y94RDzHto55Fx7w==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "10.0.3", - "Microsoft.EntityFrameworkCore.Relational": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.Data.Sqlite.Core": "10.0.11", + "Microsoft.EntityFrameworkCore.Relational": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Caching.Memory": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "resolved": "10.0.11", + "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==" + "resolved": "10.0.11", + "contentHash": "PJPtFYsZ+r+uz9qqXWUTEyKeJ1EiBGIJtqavkg9ZXijjGSFAk4Fgi5sqIxj+uAyLZwEKgexDUQXhWhvU6l3+og==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3" + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.73.1", - "contentHash": "NnDLS8QwYqO5ZZecL2oioi1LUqjh5Ewk4bMLzbgiXJbQmZhDLtKwLxL3DpGMlQAJ2G4KgEnvGPKa+OOgffeJbw==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { - "Microsoft.IdentityModel.Abstractions": "6.35.0" + "Microsoft.IdentityModel.Abstractions": "8.14.0" + } + }, + "Microsoft.Identity.Client.Broker": { + "type": "Transitive", + "resolved": "4.84.2", + "contentHash": "928ySLeGz1xtvydwq6h9tv7TbxrtA4ZzavLKUqRJh0PW6BGcEJwpI8W8vf2PlQdYemGm/oyGjdzYzdY/I8r/4A==", + "dependencies": { + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.NativeInterop": "0.20.6" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.73.1", - "contentHash": "xDztAiV2F0wI0W8FLKv5cbaBefyLD6JVaAsvgSN7bjWNCzGYzHbcOEIP5s4TJXUpQzMfUyBsFl1mC6Zmgpz0PQ==", + "resolved": "4.78.0", + "contentHash": "DYU9o+DrDQuyZxeq91GBA9eNqBvA3ZMkLzQpF7L9dTk6FcIBM1y1IHXWqiKXTvptPF7CZE59upbyUoa+FJ5eiA==", "dependencies": { - "Microsoft.Identity.Client": "4.73.1", + "Microsoft.Identity.Client": "4.78.0", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, + "Microsoft.Identity.Client.NativeInterop": { + "type": "Transitive", + "resolved": "0.20.6", + "contentHash": "noyfdMfVxyWpM6xUDmlW6gUvZ5ci24z7Qoy15JvynTQfZV5vTD2NEdsnT5Gy4ngViwgdIp5iRjRy10jAKXFAgw==" + }, "Microsoft.IdentityModel.Abstractions": { "type": "Transitive", - "resolved": "7.7.1", - "contentHash": "S7sHg6gLg7oFqNGLwN1qSbJDI+QcRRj8SuJ1jHyCmKSipnF6ZQL+tFV2NzVfGj/xmGT9TykQdQiBN+p5Idl4TA==" + "resolved": "8.14.0", + "contentHash": "iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==" }, "Microsoft.IdentityModel.JsonWebTokens": { "type": "Transitive", @@ -375,54 +386,48 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==" + "resolved": "18.9.0", + "contentHash": "Fz/qXC52VXXopzHj7v2reCKcCqSxkTDCkEDfkNAH0vni/7qK/KLMiEYtRmnUanxO2F2g2zZZ60qCpxF0AF7URA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==", + "resolved": "18.9.0", + "contentHash": "Oq+Pma/J86aZL72t71bcESvDszDsTjUfl6PIsuDdPoKTHZfNMhKamCfLD5fKx51W/u0KozXtJo2/3fnt0sgPxg==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "18.0.1", - "Newtonsoft.Json": "13.0.3" + "Microsoft.TestPlatform.ObjectModel": "18.9.0" } }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "resolved": "2.1.12", + "contentHash": "mAgscpQMLw5/nfA1Q5oJVAT29yROUo1ifZGbbTpx/lwZpSxMUGoYbKfmvdm8oXER+RzxqBmmQzeBEVKfeHv2nw==", "dependencies": { - "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", - "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + "SQLitePCLRaw.lib.e_sqlite3": "2.1.12", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.12" } }, "SQLitePCLRaw.core": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==" + "resolved": "2.1.12", + "contentHash": "ETpNw9DY3ckWLgRRAeCHj+GKOuPi61aeczkXhgHexUvqoZBAYg8RYESE2J7O1M7+o6QbdSEZwrw9bfqztUVWXg==" }, "SQLitePCLRaw.lib.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==" + "resolved": "2.1.12", + "contentHash": "fWi8Dbknuhgg72fWinIdjXVaqO1hHL4YBBwVLnr7e1c9TAZwJ0QE38j9syW1hwx6HaqEVTwI+O07WPdZn8Rp0w==" }, "SQLitePCLRaw.provider.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "resolved": "2.1.12", + "contentHash": "W3oH4XIfCzFrgUSDKHhN6N+dgzA5YHOR2VxX8GB6Qy7CyrJJgxPEG8NirgYWlPQC5P2jz2knSsexWu4tDUL33g==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.5.1", - "contentHash": "k2jKSO0X45IqhVOT9iQB4xralNN9foRQsRvXBTyRpAVxyzCJlG895T9qYrQWbcJ6OQXxOouJQ37x5nZH5XKK+A==", + "resolved": "1.8.0", + "contentHash": "AqRzhn0v29GGGLj/Z6gKq4lGNtvPHT4nHdG5PDJh9IfVjv/nYUVmX11hwwws1vDFeIAzrvmn0dPu8IjLtu6fAw==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.3", "System.Memory.Data": "8.0.1" @@ -430,17 +435,17 @@ }, "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "dvjqKp+2LpGid6phzrdrS/2mmEPxFl3jE1+L7614q4ZChKbLJCpHXg6sBILlCCED1t//EE+un/UdAetzIMpqnw==", + "resolved": "9.0.11", + "contentHash": "vPinosRgEk1CGjohbv0pDuz6gPprkWo5xvBTjOJjks5JOgOEWvPndq5NngGxiTcBcy1+k9GwzZ1c3Bp2fU5ezw==", "dependencies": { - "System.Diagnostics.EventLog": "9.0.4", - "System.Security.Cryptography.ProtectedData": "9.0.4" + "System.Diagnostics.EventLog": "9.0.11", + "System.Security.Cryptography.ProtectedData": "9.0.11" } }, "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "getRQEXD8idlpb1KW56XuxImMy0FKp2WJPDf3Qr0kI/QKxxJSftqfDFVo0DZ3HCJRLU73qHSruv5q2l5O47jQQ==" + "resolved": "9.0.11", + "contentHash": "2Us/NchH6SM69NYWzf8NRyeftdv3ILso8LMiMdAjT7ECTiZKzddiXWhAyQj6ZzbddoAOHS9GdlPbAAkwCPID3Q==" }, "System.IdentityModel.Tokens.Jwt": { "type": "Transitive", @@ -458,13 +463,13 @@ }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "cUFTcMlz/Qw9s90b2wnWSCvHdjv51Bau9FQqhsr4TlwSe1OX+7SoXUqphis5G74MLOvMOCghxPPlEqOdCrVVGA==" + "resolved": "9.0.11", + "contentHash": "YS2YqtN6fFjlTDIQI+ucjbbrEvwMn796r+VLTQRr/5Oy6g+i+m7nIou83KnJCnAcKna2I+5eVJRks6SoHSpetQ==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "o94k2RKuAce3GeDMlUvIXlhVa1kWpJw95E6C9LwW0KlG0nj5+SgCiIxJ2Eroqb9sLtG1mEMbFttZIBZ13EJPvQ==" + "resolved": "9.0.11", + "contentHash": "s8yUYuYYu+PAwvBdhLG1KyrGrk9gkYeuPxfAsXsTqqWyepwSyEw8hAaflW4nO98NG52YpYI1am2+9o+79h2RtQ==" }, "xunit.abstractions": { "type": "Transitive", @@ -509,7 +514,7 @@ "efpagination": { "type": "Project", "dependencies": { - "Microsoft.EntityFrameworkCore": "[10.0.3, )" + "Microsoft.EntityFrameworkCore": "[10.0.11, )" } }, "efpagination.analyzers": {