Files
OpenNest/OpenNest.Server/NestQueryRequest.cs
T
ajandClaude Opus 5.5 7ce5e73a82 refactor: share duplicated paging, HTTP and helper code
Roslyn duplicate scans found the same code copied across layers:
- Data: the three browse sessions share BrowseSession<TPage, TSort>;
  the remote clients share NestServerHttp (JSON options, If-Match,
  error text).
- WinForms: five paged lists share BrowsePager for loading text,
  navigation, sort glyphs and failure messages. The Add Drawings to
  Release, Drawing Library and set browser dialogs now dispose their
  browse sessions.
- Server: NestQueryRequest reuses DrawingLibraryRequest's helpers;
  revision history queries share count and paging binds.
- Engine: RemnantPlateNester delegates to DefaultPlateNester;
  CuttingPlanService reuses JointCuttingPlanSearch.Map.
- Core: LineArc and CleanHole lead-ins share LineArcApproach with
  unchanged arithmetic order, so output is bit-identical.
- IO: Dxf.GetGeometry overloads share one reader; .nest JSON test
  helpers move to NestArchiveJson.

Also fixes the production compiler warnings (CS8632, CS8603, CS0108)
and moves a test off an obsolete forwarding API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:45:36 -04:00

104 lines
3.7 KiB
C#

using System.Globalization;
using OpenNest.Data;
namespace OpenNest.Server;
/// <summary>
/// Parses <c>GET /api/nests/query</c> parameters. Unknown, repeated, non-integer or
/// out-of-range values are rejected rather than ignored or clamped, so a request
/// can never silently receive a broader result than it asked for.
/// </summary>
internal static class NestQueryRequest
{
private static readonly string[] Keys =
{
"search", "sort", "order", "offset", "limit", "status", "material", "materialGrade", "thickness",
};
public static bool TryParse(IQueryCollection values, out NestQuery query, out string error)
{
query = new NestQuery();
foreach (var key in values.Keys)
{
if (!Keys.Contains(key, StringComparer.OrdinalIgnoreCase))
{
error = $"Unknown query parameter '{key}'.";
return false;
}
if (values[key].Count > 1)
{
error = $"Query parameter '{key}' was given more than once.";
return false;
}
}
var sort = NestSortField.SavedAt;
if (values.TryGetValue("sort", out var sortText)
&& !DrawingLibraryRequest.TryEnumName(sortText.ToString(), out sort))
{
error = $"sort must be one of: {string.Join(", ", Enum.GetNames<NestSortField>().Select(DrawingLibraryRequest.CamelCase))}.";
return false;
}
var descending = true;
if (values.TryGetValue("order", out var orderText))
{
if (string.Equals(orderText, "asc", StringComparison.OrdinalIgnoreCase))
{
descending = false;
}
else if (!string.Equals(orderText, "desc", StringComparison.OrdinalIgnoreCase))
{
error = "order must be asc or desc.";
return false;
}
}
NestStatus? status = null;
if (values.TryGetValue("status", out var statusText))
{
if (!DrawingLibraryRequest.TryEnumName<NestStatus>(statusText.ToString(), out var parsed))
{
error = "status must be draft, quote, toBeCut or hasBeenCut.";
return false;
}
status = parsed;
}
double? thickness = null;
if (values.TryGetValue("thickness", out var thicknessText))
{
if (!double.TryParse(thicknessText.ToString(), NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
CultureInfo.InvariantCulture, out var parsed))
{
error = "thickness must be a number, zero or greater.";
return false;
}
thickness = parsed;
}
var offset = 0;
var limit = NestQuery.DefaultLimit;
if (!DrawingLibraryRequest.TryInteger(values, "offset", ref offset, out error)
|| !DrawingLibraryRequest.TryInteger(values, "limit", ref limit, out error))
return false;
query = new NestQuery
{
Search = values["search"].ToString(),
Status = status,
// Present but blank is a filter too: a blank grade asks for nests without one.
Material = values.TryGetValue("material", out var material) ? material.ToString() : null,
MaterialGrade = values.TryGetValue("materialGrade", out var grade) ? grade.ToString() : null,
Thickness = thickness,
Sort = sort,
Descending = descending,
Offset = offset,
Limit = limit,
};
error = query.GetValidationError() ?? "";
return error.Length == 0;
}
}