π Search Users with Filters
Now that you can create users, letβs implement a query endpoint that allows clients to search for users using filters, pagination, and sorting β all powered by PotatoFinder.
π Place all files for this feature in:
Potato.Example.Application/Features/Users/Search/
π‘ Protip: Add a
Usings.csfile to avoid repeating common imports:global using static LanguageExt.Prelude; global using static Potato.PotatoModule; global using LanguageExt; global using Potato; global using Potato.Example.Domain;
π SearchUsersQuery.cs
namespace Potato.Examples.Application;
/// <summary>
/// A flexible search query for users using PotatoFilter, pagination, and optional sorting.
/// </summary>
/// <param name="Filter">The filter to apply to the node tree.</param>
/// <param name="Cursor">Pagination options (e.g. first, after).</param>
/// <param name="Sort">Optional sorting by property and direction.</param>
public record SearchUsersQuery(
PotatoFilter Filter,
PotatoCursor Cursor,
PotatoSort? Sort
) : PotatoQuery<PotatoEdgeCollection<UserNode>>;
π SearchUsersQuery.Handler.cs
using System;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Potato.Examples.Application;
/// <summary>
/// Handles execution of <see cref="SearchUsersQuery"/> to retrieve paginated user nodes.
/// Automatically enforces type filtering to ensure only <see cref="UserNode"/> results are returned.
/// </summary>
public class SearchUsersQueryHandler(
ILogger<SearchUsersQueryHandler> logger,
PotatoFinder<Node> finder
) : PotatoQueryHandler<SearchUsersQuery, PotatoEdgeCollection<UserNode>>
{
/// <summary>
/// Handles the <see cref="SearchUsersQuery"/> by applying the provided filter, sort, and pagination parameters.
/// Ensures only <see cref="UserNode"/> results are returned, even if the input filter is broader.
/// </summary>
/// <param name="query">The search query containing filter, cursor, and optional sort.</param>
/// <param name="cancellationToken">A cancellation token for cooperative request cancellation.</param>
/// <returns>A paginated collection of <see cref="UserNode"/> results or a failure.</returns>
public override async Task<Either<PotatoFailure, PotatoEdgeCollection<UserNode>>> Handle(SearchUsersQuery query, CancellationToken cancellationToken)
{
try
{
using Activity? activity = Activity.Current?.Source.StartActivity();
logger.LogQueryRecieved(query);
return await finder.Find(
Filter: PotatoFilter.And(query.Filter, PotatoFilter.OfType<UserNode>()),
Cursor: query.Cursor,
Sort: query.Sort
)
.MapAsync(c => c.Cast<UserNode>())
.HandlerFinished(logger, query);
}
catch (Exception ex)
{
logger.LogQueryFailed(query, ex);
return UnexpectedFailure(ex);
}
}
}
π SearchUsersQuery.Extension.cs
using Microsoft.AspNetCore.Mvc;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Maps the search endpoint for querying <see cref="UserNode"/>s.
/// </summary>
public static class SearchUsersQueryExtension
{
public static RouteHandlerBuilder MapSearchUsers(this WebApplication app)
{
return app.MapPut("/users/search", async (
[FromBody] SearchUsersQuery query,
[FromServices] PotatoCommandExecutor executor
) =>
{
return await executor.Execute<SearchUsersQuery, PotatoEdgeCollection<UserNode>>(query);
});
}
}
π Register the endpoint in Program.cs:
app.MapSearchUsers();
π Potato.Example.Application.http
Use this file in VS Code with the REST Client extension to test both CreateUserCommand and SearchUsersQuery.
@host = http://localhost
@port = 5122
PUT {{host}}:{{port}}/users/create
Content-Type: application/json
{
"username": "JohnDoe"
}
###
PUT {{host}}:{{port}}/users/search
Content-Type: application/json
{
"filter": {
"$type": "PotatoPropertyEqualsFilter",
"property": "Username",
"value": "JohnDoe"
},
"cursor": {
"first": 10
},
"sort": {
"property": "Username",
"direction": "Ascending"
}
}
β Summary
You now have:
- β
A
SearchUsersQueryto filter, page, and sort users - β
A handler that uses
PotatoFinderand filters onlyUserNodeentries - β
A minimal API endpoint wired with
PotatoCommandExecutor - β
A
.httpfile for fast testing with REST Client
β‘οΈ Next: Add OpenAPI schema filters and examples to your Swagger docs.
Would you like help drafting the OpenAPI schema filter for UserNode?