mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-03 13:42:20 -04:00
SQLite LIKE stops matching at an embedded NUL, so search=Alpha%00x behaved like 'Alpha' followed by a wildcard and returned rows that do not contain the whole search text. The shared query validation now rejects NUL, so the server answers 400 and the client throws before sending.
51 lines
2.1 KiB
C#
51 lines
2.1 KiB
C#
namespace OpenNest.Data;
|
|
|
|
/// <summary>
|
|
/// One bounded page request for browsing saved nests (<c>GET /api/nests/query</c>).
|
|
/// The server filters and pages in SQL; the client and server share these bounds.
|
|
/// </summary>
|
|
public sealed class NestQuery
|
|
{
|
|
public const int DefaultLimit = 100;
|
|
public const int MaxLimit = 500;
|
|
public const int MaxSearchLength = 200;
|
|
|
|
/// <summary>
|
|
/// Case-insensitive substring matched against name, customer, material, made by,
|
|
/// comments and status (stored and display names). Blank means no filter.
|
|
/// </summary>
|
|
public string Search { get; init; } = "";
|
|
|
|
/// <summary>Column to order by; ties are broken by id in the same direction.</summary>
|
|
public NestSortField Sort { get; init; } = NestSortField.SavedAt;
|
|
|
|
/// <summary>Largest/newest/Z first when true (the default, newest saved first).</summary>
|
|
public bool Descending { get; init; } = true;
|
|
|
|
/// <summary>Number of matching records to skip, zero or greater.</summary>
|
|
public int Offset { get; init; }
|
|
|
|
/// <summary>Maximum records returned, 1 to <see cref="MaxLimit"/>.</summary>
|
|
public int Limit { get; init; } = DefaultLimit;
|
|
|
|
/// <summary>The search text as matched: trimmed, never null.</summary>
|
|
public string NormalizedSearch => (Search ?? "").Trim();
|
|
|
|
/// <summary>The first violated bound, or null when the query may be sent.</summary>
|
|
public string? GetValidationError()
|
|
{
|
|
if (!Enum.IsDefined(Sort))
|
|
return "sort must be a supported column.";
|
|
if (Offset < 0)
|
|
return "offset must be zero or greater.";
|
|
if (Limit < 1 || Limit > MaxLimit)
|
|
return $"limit must be between 1 and {MaxLimit}.";
|
|
if (NormalizedSearch.Length > MaxSearchLength)
|
|
return $"search must be at most {MaxSearchLength} characters.";
|
|
// SQLite LIKE stops at an embedded NUL, which would turn the rest of the text into a wildcard.
|
|
if (NormalizedSearch.Contains('\0'))
|
|
return "search must not contain NUL characters.";
|
|
return null;
|
|
}
|
|
}
|