Skip to content

Requests and URI Builder RU

Алексей . edited this page Jul 26, 2026 · 1 revision

Запросы и построитель URI

English Русский

Эта страница описывает три записи-запроса (LogsQuery, AdminActivityQuery, TopOperationsQuery) и статический класс LogsRequestUriBuilder, который превращает их в относительные строки URI, а также все правила проверки и нормализации, применяемые построителем.

Модели запросов

Все три записи находятся в пространстве имён LogsParser.Models и являются запечатанными неизменяемыми записями.

LogsQuery

public sealed record LogsQuery(
    int ServerId,
    IReadOnlyCollection<string>? Filters = null,
    DateTime? PeriodFrom = null,
    DateTime? PeriodTo = null,
    string? Player = null,
    string? Target = null,
    IPAddress? IpAddress = null,
    IReadOnlyDictionary<string, string>? AdditionalParameters = null,
    int Page = 1,
    int Limit = 1000,
    string Sort = "desc");
Параметр Тип По умолчанию Описание
ServerId int — (обязательный) Номер игрового сервера. Выводится как server_number. Должен быть больше нуля.
Filters IReadOnlyCollection<string>? null Коды типов логов. Каждое непустое значение выводится отдельной записью type%5B%5D=.
PeriodFrom DateTime? null Начало периода. Выводится как min_period; при null опускается.
PeriodTo DateTime? null Конец периода. Выводится как max_period; при null опускается.
Player string? null Ник действующего игрока. Выводится как player; опускается при null, пустой строке или пробелах.
Target string? null Ник игрока-цели. Выводится как target; опускается при null, пустой строке или пробелах.
IpAddress IPAddress? null Фильтр по IP. Выводится как ip через IPAddress.ToString(); при null опускается.
AdditionalParameters IReadOnlyDictionary<string, string>? null Параметры dynamic[n], специфичные для фильтра. Ключи должны соответствовать ^dynamic\[\d+\]$.
Page int 1 Номер страницы. Выводится как page. Должен быть больше нуля.
Limit int 1000 Число строк на странице. Выводится как limit после приведения к 100, 500 или 1000.
Sort string "desc" Порядок сортировки. Допустимы только "desc" и "asc", без учёта регистра.

Filters и AdditionalParameters — единственные коллекции среди членов записи, и обе необязательны: null означает просто «такого параметра нет в строке запроса».

using System.Net;
using LogsParser.Models;

var query = new LogsQuery(
    ServerId: 18,
    Filters: ["connect", "kill"],
    PeriodFrom: new DateTime(2026, 5, 1, 3, 0, 0),
    PeriodTo: new DateTime(2026, 6, 1, 3, 0, 0),
    Player: "Ivan",
    IpAddress: IPAddress.Parse("127.0.0.1"),
    Page: 2,
    Limit: 500);

AdminActivityQuery

public sealed record AdminActivityQuery(DateTime PeriodFrom, DateTime PeriodTo);
Параметр Тип По умолчанию Описание
PeriodFrom DateTime — (обязательный) Начало отчётного периода. Выводится как min_period.
PeriodTo DateTime — (обязательный) Конец отчётного периода. Выводится как max_period.

Оба члена не допускают null, поэтому оба параметра запроса присутствуют всегда. Построитель не проверяет, что PeriodFrom предшествует PeriodTo.

using LogsParser.Models;

var query = new AdminActivityQuery(
    new DateTime(2026, 5, 1, 3, 0, 0),
    new DateTime(2026, 6, 1, 3, 0, 0));

TopOperationsQuery

public sealed record TopOperationsQuery(string? Filter = null, DateTime? Date = null);
Параметр Тип По умолчанию Описание
Filter string? null Код типа операции. Выводится как type; опускается при null, пустой строке или пробелах.
Date DateTime? null Дата отчёта. Выводится как date; при null опускается.

Оба члена необязательны. Если оба остаются null, построитель возвращает голую строку top?.

using LogsParser.Models;

var query = new TopOperationsQuery("transfer", new DateTime(2026, 6, 1));

LogsRequestUriBuilder

public static partial class LogsRequestUriBuilder

Пространство имён — LogsParser (корневое, не LogsParser.Models). Класс объявлен partial, потому что проверка ключей dynamic[n] использует сгенерированный исходниками метод с атрибутом [GeneratedRegex].

Все три метода возвращают относительные строки URI, предназначенные для ParserRequest.RelativeUri, которые разрешаются относительно базового URI источника данных (по умолчанию https://arizonarp.logsparser.info/). Формы результата различаются:

Метод Форма результата Пример
BuildLogsUri начинается с ? ?server_number=18&sort=desc&limit=1000&page=1
BuildAdminActivityUri начинается с /admins? /admins?min_period=…&max_period=…
BuildTopOperationsUri начинается с top? (без ведущего слеша) top?type=transfer&date=…

BuildLogsUri

public static string BuildLogsUri(LogsQuery query)

Строит строку запроса логов. Параметры выводятся в фиксированном порядке: server_number, sort, limit, page, затем все записи type%5B%5D, затем min_period, max_period, player, target, ip, и в конце записи AdditionalParameters в порядке перечисления самого словаря.

Проверки выполняются в том же порядке, поэтому о некорректном Sort сообщается раньше, чем о некорректном Limit, а о некорректном Limit — раньше, чем о некорректном Page.

Исключение Когда
ArgumentNullException query равен null.
ArgumentOutOfRangeException ServerId равен нулю или отрицателен; Limit равен нулю или отрицателен; Page равен нулю или отрицателен.
ArgumentException Sort не равен "desc" или "asc"; ключ в AdditionalParameters совпадает с зарезервированным или не соответствует ^dynamic\[\d+\]$.
using LogsParser;
using LogsParser.Models;

var uri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(ServerId: 18));
// ?server_number=18&sort=desc&limit=1000&page=1

BuildAdminActivityUri

public static string BuildAdminActivityUri(AdminActivityQuery query)

Строит URI активности администраторов. Обе даты выводятся всегда, поэтому результат всегда содержит два параметра.

Исключение Когда
ArgumentNullException query равен null.
using LogsParser;
using LogsParser.Models;

var uri = LogsRequestUriBuilder.BuildAdminActivityUri(new AdminActivityQuery(
    new DateTime(2026, 5, 1, 3, 0, 0),
    new DateTime(2026, 6, 1, 3, 0, 0)));
// /admins?min_period=2026-05-01%2003%3A00%3A00&max_period=2026-06-01%2003%3A00%3A00

BuildTopOperationsUri

public static string BuildTopOperationsUri(TopOperationsQuery query)

Строит URI топа операций. Filter выводится как type, а Date — как date; оба пропускаются, если отсутствуют.

Исключение Когда
ArgumentNullException query равен null.
using LogsParser;
using LogsParser.Models;

var uri = LogsRequestUriBuilder.BuildTopOperationsUri(
    new TopOperationsQuery("transfer", new DateTime(2026, 6, 1)));
// top?type=transfer&date=2026-06-01%2000%3A00%3A00

Правила проверки и нормализации

ServerId

Должен быть больше нуля. Ноль или отрицательное значение вызывает ArgumentOutOfRangeException с сообщением ServerId must be greater than zero. Это первая проверка после проверки на null.

// выбрасывает ArgumentOutOfRangeException
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(0));

Page

Должен быть больше нуля. Ноль или отрицательное значение вызывает ArgumentOutOfRangeException с сообщением page must be greater than zero. Любое положительное значение передаётся без изменений.

// выбрасывает ArgumentOutOfRangeException
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Page: 0));

Limit

Сайт поддерживает ровно три размера страницы — 100, 500 и 1000, — поэтому любое другое положительное значение округляется вниз до ближайшего поддерживаемого. Ноль или отрицательное значение вызывает ArgumentOutOfRangeException с сообщением limit must be greater than zero.

Входной Limit Выводимый limit
<= 0 выбрасывает ArgumentOutOfRangeException
1 … 99 100
100 100
101 … 499 100
500 500
501 … 999 500
1000 1000
> 1000 1000

Кратко: всё, что меньше 500, становится 100; всё, что меньше 1000, становится 500; 1000 и больше становится 1000; значения 100, 500 и 1000 проходят без изменений.

LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Limit: 250)); // …&limit=100&…
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Limit: 600)); // …&limit=500&…

Sort

Допустимы только "desc" (сначала новые) и "asc" (сначала старые). Сравнение выполняется без учёта регистра, а значение приводится к нижнему регистру перед записью в строку запроса, поэтому "ASC" даёт sort=asc. Всё остальное — включая пустую строку, строку из пробелов и null — вызывает ArgumentException с сообщением Sort '<значение>' is not supported. Use 'desc' (newest first) or 'asc' (oldest first).

LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Sort: "ASC"));        // …&sort=asc&…
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Sort: "descending")); // выбрасывает ArgumentException

Filters

Каждый фильтр выводится отдельной повторяющейся записью с заранее закодированным ключом type[]: type%5B%5D=<значение>. Записи, равные null, пустые или состоящие из пробелов, молча пропускаются; само значение кодируется для URL.

var uri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Filters: ["connect", "kill"]));
// содержит type%5B%5D=connect и type%5B%5D=kill

Даты

PeriodFrom, PeriodTo и Date форматируются через ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), после чего результат кодируется для URL: пробел становится %20, а двоеточия — %3A. Преобразование часового пояса не выполняется — DateTime используется ровно в том виде, в каком он передан.

new DateTime(2026, 5, 1, 3, 0, 0)   // → min_period=2026-05-01%2003%3A00%3A00

AdditionalParameters

Записи, у которых ключ или значение равны null, пусты или состоят из пробелов, молча пропускаются. Каждый оставшийся ключ проверяется в два этапа, и оба выбрасывают ArgumentException:

  1. Конфликт с зарезервированным ключом. Ключ не должен совпадать ни с одним параметром, который построитель формирует сам. Набор зарезервированных ключей сравнивается через StringComparer.OrdinalIgnoreCase и содержит ровно:

    server_number, sort, limit, page, type[], type, min_period, max_period, player, target, ip

    Сообщение: Additional parameter '<ключ>' conflicts with a reserved logs query parameter.

  2. Форма ключа. Ключ должен соответствовать регулярному выражению ^dynamic\[\d+\]$ (сопоставление без учёта регистра). Сообщение: Additional parameter '<ключ>' is invalid. Only dynamic[n] parameters from the logs page are supported.

Ключ и значение кодируются для URL, поэтому dynamic[0] записывается как dynamic%5B0%5D.

var uri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(
    18,
    AdditionalParameters: new Dictionary<string, string> { ["dynamic[0]"] = "42" }));
// содержит dynamic%5B0%5D=42

// выбрасывает ArgumentException — зарезервированный ключ
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(
    18,
    AdditionalParameters: new Dictionary<string, string> { ["player"] = "x" }));

// выбрасывает ArgumentException — ключ не вида dynamic[n]
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(
    18,
    AdditionalParameters: new Dictionary<string, string> { ["foo"] = "x" }));

Откуда берутся допустимые ключи dynamic[n]

Ключи dynamic[n] не произвольны: каждый принадлежит конкретному фильтру логов и публикуется самим сайтом в разметке страницы логов. До вас они доходят через каталог фильтров как LogsFilterCatalog.Filters[].AdditionalParameters[].QueryKey:

public sealed record LogsFilterCatalog(
    IReadOnlyList<LogsFilterDefinition> Filters,
    LogsAccount? Account = null);
public sealed record LogsFilterDefinition(
    string Code,
    string Name,
    IReadOnlyList<LogsFilterAdditionalParameter> AdditionalParameters);
public sealed record LogsFilterAdditionalParameter(
    string QueryKey,
    string Label);

QueryKey — это и есть строка dynamic[n], используемая как ключ словаря, а Label — понятная человеку подпись, которую сайт показывает рядом с полем ввода. Прочитайте каталог один раз, выберите нужный фильтр и передайте его QueryKey напрямую в LogsQuery.AdditionalParameters:

using LogsParser;
using LogsParser.Models;

var catalog = await client.GetLogsFilterCatalogAsync();

var filter = catalog.Filters.First(definition => definition.Code == "transfer");
var extra = filter.AdditionalParameters.First(parameter => parameter.Label.Contains("сумма"));

var page = await client.GetLogsAsync(new LogsQuery(
    ServerId: 18,
    Filters: [filter.Code],
    AdditionalParameters: new Dictionary<string, string> { [extra.QueryKey] = "1000000" }));

Поскольку ключи берутся с живой страницы, у фильтра без дополнительных полей ввода список AdditionalParameters просто пуст — коллекция никогда не равна null.

Разобранный пример

using System.Net;
using LogsParser;
using LogsParser.Models;

var query = new LogsQuery(
    ServerId: 18,
    Filters: ["connect", "kill"],
    PeriodFrom: new DateTime(2026, 5, 1, 3, 0, 0),
    PeriodTo: new DateTime(2026, 6, 1, 3, 0, 0),
    Player: "Ivan",
    IpAddress: IPAddress.Parse("127.0.0.1"),
    Page: 2,
    Limit: 600,
    Sort: "ASC");

Console.WriteLine(LogsRequestUriBuilder.BuildLogsUri(query));

Вывод:

?server_number=18&sort=asc&limit=500&page=2&type%5B%5D=connect&type%5B%5D=kill&min_period=2026-05-01%2003%3A00%3A00&max_period=2026-06-01%2003%3A00%3A00&player=Ivan&ip=127.0.0.1

В одной этой строке видны три нормализации: Sort: "ASC" приведён к нижнему регистру и стал sort=asc, Limit: 600 округлён вниз до limit=500, а обе даты отформатированы по инвариантной культуре и закодированы для URL. Target остался null, поэтому параметра target в выводе нет вовсе.

HTTP здесь не участвует

LogsRequestUriBuilder — статический класс из чистых функций: он принимает запись, возвращает строку и никогда не открывает соединение, не трогает cookie и не читает конфигурацию. Благодаря этому его можно использовать самостоятельно — чтобы записать в лог точный URI, который сформирует вызов, для модульных тестов или для построения запросов, которые вы собираетесь выполнить собственным HTTP-стеком вместо LogsParserHttpDataSource.

using LogsParser;
using LogsParser.Models;

var relativeUri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Limit: 100));
var absolute = new Uri(new Uri("https://arizonarp.logsparser.info/"), relativeUri);

Console.WriteLine(absolute);

Ровно эту же строку LogsParserClient передаёт источнику данных в виде ParserRequest.RelativeUri:

public sealed record ParserRequest(string RelativeUri, ICookieStorage? CookieStorage = null);

См. также

Clone this wiki locally