using System.Globalization;
using OpenNest.Data;
namespace OpenNest.Server;
///
/// Parses GET /api/nests/query 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.
///
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().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(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;
}
}