-
Notifications
You must be signed in to change notification settings - Fork 0
Requests and URI Builder
This page documents the three query records that describe a request (LogsQuery, AdminActivityQuery,
TopOperationsQuery) and the static LogsRequestUriBuilder that turns them into relative URI strings, together
with every validation and normalisation rule the builder applies.
All three records live in the LogsParser.Models namespace and are sealed immutable records.
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");| Parameter | Type | Default | Description |
|---|---|---|---|
ServerId |
int |
— (required) | Game server number. Emitted as server_number. Must be greater than zero. |
Filters |
IReadOnlyCollection<string>? |
null |
Log type codes. Each non-blank value is emitted as a separate type%5B%5D= entry. |
PeriodFrom |
DateTime? |
null |
Start of the period. Emitted as min_period; omitted when null. |
PeriodTo |
DateTime? |
null |
End of the period. Emitted as max_period; omitted when null. |
Player |
string? |
null |
Nickname of the acting player. Emitted as player; omitted when null, empty or whitespace. |
Target |
string? |
null |
Nickname of the target player. Emitted as target; omitted when null, empty or whitespace. |
IpAddress |
IPAddress? |
null |
IP filter. Emitted as ip using IPAddress.ToString(); omitted when null. |
AdditionalParameters |
IReadOnlyDictionary<string, string>? |
null |
Filter-specific dynamic[n] parameters. Keys must match ^dynamic\[\d+\]$. |
Page |
int |
1 |
Page number. Emitted as page. Must be greater than zero. |
Limit |
int |
1000 |
Rows per page. Emitted as limit after being snapped to 100, 500 or 1000. |
Sort |
string |
"desc" |
Ordering. Only "desc" and "asc" are accepted, case-insensitively. |
Filters and AdditionalParameters are the only collection-typed members, and both are optional — null simply
means "no such parameter in the query string".
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);public sealed record AdminActivityQuery(DateTime PeriodFrom, DateTime PeriodTo);| Parameter | Type | Default | Description |
|---|---|---|---|
PeriodFrom |
DateTime |
— (required) | Start of the reporting period. Emitted as min_period. |
PeriodTo |
DateTime |
— (required) | End of the reporting period. Emitted as max_period. |
Both members are non-nullable, so both query parameters are always present. The builder does not check that
PeriodFrom precedes PeriodTo.
using LogsParser.Models;
var query = new AdminActivityQuery(
new DateTime(2026, 5, 1, 3, 0, 0),
new DateTime(2026, 6, 1, 3, 0, 0));public sealed record TopOperationsQuery(string? Filter = null, DateTime? Date = null);| Parameter | Type | Default | Description |
|---|---|---|---|
Filter |
string? |
null |
Operation type code. Emitted as type; omitted when null, empty or whitespace. |
Date |
DateTime? |
null |
Report date. Emitted as date; omitted when null. |
Both members are optional. With both left at null the builder produces the bare string top?.
using LogsParser.Models;
var query = new TopOperationsQuery("transfer", new DateTime(2026, 6, 1));public static partial class LogsRequestUriBuilderNamespace LogsParser (the root namespace, not LogsParser.Models). The class is partial because its
dynamic[n] key validation uses a [GeneratedRegex] source-generated method.
The three methods return relative URI strings intended for ParserRequest.RelativeUri, which is resolved against
the data source's base URI (https://arizonarp.logsparser.info/ by default). The three shapes differ:
| Method | Returned shape | Example |
|---|---|---|
BuildLogsUri |
starts with ?
|
?server_number=18&sort=desc&limit=1000&page=1 |
BuildAdminActivityUri |
starts with /admins?
|
/admins?min_period=…&max_period=… |
BuildTopOperationsUri |
starts with top? (no leading slash) |
top?type=transfer&date=… |
public static string BuildLogsUri(LogsQuery query)Builds the logs query string. Parameters are emitted in a fixed order: server_number, sort, limit, page,
then every type%5B%5D entry, then min_period, max_period, player, target, ip, then the
AdditionalParameters entries in the dictionary's own enumeration order.
Validation runs in that same order, so an invalid Sort is reported before an invalid Limit, and an invalid
Limit before an invalid Page.
| Throws | When |
|---|---|
ArgumentNullException |
query is null. |
ArgumentOutOfRangeException |
ServerId is zero or negative; Limit is zero or negative; Page is zero or negative. |
ArgumentException |
Sort is not "desc" or "asc"; an AdditionalParameters key collides with a reserved key or does not match ^dynamic\[\d+\]$. |
using LogsParser;
using LogsParser.Models;
var uri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(ServerId: 18));
// ?server_number=18&sort=desc&limit=1000&page=1public static string BuildAdminActivityUri(AdminActivityQuery query)Builds the admin activity URI. Both dates are always emitted, so the result always contains two parameters.
| Throws | When |
|---|---|
ArgumentNullException |
query is 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%3A00public static string BuildTopOperationsUri(TopOperationsQuery query)Builds the top operations URI. Filter is emitted as type and Date as date; both are skipped when absent.
| Throws | When |
|---|---|
ArgumentNullException |
query is 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%3A00Must be greater than zero. Zero or a negative value throws ArgumentOutOfRangeException with the message
ServerId must be greater than zero. This is the first check after the null check.
// throws ArgumentOutOfRangeException
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(0));Must be greater than zero. Zero or a negative value throws ArgumentOutOfRangeException with the message
page must be greater than zero. Any positive value is passed through unchanged.
// throws ArgumentOutOfRangeException
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Page: 0));The site supports exactly three page sizes — 100, 500 and 1000 — so any other positive value is snapped down to
the nearest supported one. Zero or a negative value throws ArgumentOutOfRangeException with the message
limit must be greater than zero.
Input Limit
|
Emitted limit
|
|---|---|
<= 0 |
throws ArgumentOutOfRangeException
|
1 … 99
|
100 |
100 |
100 |
101 … 499
|
100 |
500 |
500 |
501 … 999
|
500 |
1000 |
1000 |
> 1000 |
1000 |
In short: anything below 500 becomes 100, anything below 1000 becomes 500, and 1000 or more becomes 1000; 100, 500 and 1000 pass through untouched.
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Limit: 250)); // …&limit=100&…
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Limit: 600)); // …&limit=500&…Only "desc" (newest first) and "asc" (oldest first) are accepted. The comparison is case-insensitive, and the
value is lowercased before being written into the query string, so "ASC" produces sort=asc. Anything else —
including an empty string, a whitespace string and null — throws ArgumentException with the message
Sort '<value>' 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")); // throws ArgumentExceptionEach filter is emitted as its own repeated entry with a pre-encoded type[] key: type%5B%5D=<value>. Entries
that are null, empty or whitespace are skipped silently; the value itself is URL-encoded.
var uri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(18, Filters: ["connect", "kill"]));
// contains type%5B%5D=connect and type%5B%5D=killPeriodFrom, PeriodTo and Date are formatted with ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
and the result is then URL-encoded, so the space becomes %20 and the colons become %3A. No time-zone
conversion is performed — the DateTime is used exactly as given.
new DateTime(2026, 5, 1, 3, 0, 0) // → min_period=2026-05-01%2003%3A00%3A00Entries whose key or value is null, empty or whitespace are skipped silently. Every remaining key is validated
in two steps, both of which throw ArgumentException:
-
Reserved-key collision. The key must not be one of the parameters the builder emits itself. The reserved set is compared with
StringComparer.OrdinalIgnoreCaseand contains exactly:server_number,sort,limit,page,type[],type,min_period,max_period,player,target,ipMessage:
Additional parameter '<key>' conflicts with a reserved logs query parameter. -
Shape. The key must match the regular expression
^dynamic\[\d+\]$(matched case-insensitively). Message:Additional parameter '<key>' is invalid. Only dynamic[n] parameters from the logs page are supported.
Key and value are both URL-encoded, so dynamic[0] is written as dynamic%5B0%5D.
var uri = LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(
18,
AdditionalParameters: new Dictionary<string, string> { ["dynamic[0]"] = "42" }));
// contains dynamic%5B0%5D=42
// throws ArgumentException — reserved key
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(
18,
AdditionalParameters: new Dictionary<string, string> { ["player"] = "x" }));
// throws ArgumentException — not a dynamic[n] key
LogsRequestUriBuilder.BuildLogsUri(new LogsQuery(
18,
AdditionalParameters: new Dictionary<string, string> { ["foo"] = "x" }));The dynamic[n] keys are not arbitrary: each one belongs to a specific log filter and is published by the site
itself in the logs page markup. They reach you through the filter catalogue as
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 is the dynamic[n] string to use as a dictionary key, and Label is the human-readable caption the
site shows next to the input. Read the catalogue once, pick the filter you need, and feed its QueryKey straight
into 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" }));Because the keys come from the live page, a filter with no extra inputs simply has an empty
AdditionalParameters list — the collection is never 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));Output:
?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
Three normalisations are visible in that one line: Sort: "ASC" was lowercased to sort=asc, Limit: 600 was
snapped down to limit=500, and both dates were formatted with the invariant culture and URL-encoded. Target
was left null, so no target parameter appears at all.
LogsRequestUriBuilder is a static class of pure functions: it takes a record, returns a string, and never opens
a connection, touches cookies or reads configuration. That makes it usable on its own — for logging the exact URI
a call will produce, for unit tests, or for building requests you intend to execute through your own HTTP stack
instead of 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);The same string is what LogsParserClient passes to the data source as ParserRequest.RelativeUri:
public sealed record ParserRequest(string RelativeUri, ICookieStorage? CookieStorage = null);- LogsParserClient — the facade that calls these builders for you
- Models — the response records these requests return
- Parsers — turning fetched HTML into models without HTTP
-
Transport and Authentication — how
RelativeUriis executed - Getting Started — the first request end to end
- Home
LogsParser · CC BY-NC 4.0 — non-commercial use only / только некоммерческое использование
English
- Home
- Getting Started
- LogsParserClient
- Requests and URI Builder
- Models
- Parsers
- Transport and Authentication
- Cookie Storage
- Dependency Injection
- Logging
- Exceptions
Русский
- Главная
- Начало работы
- LogsParserClient
- Запросы и построитель URI
- Модели
- Парсеры
- Транспорт и авторизация
- Хранение cookies
- Внедрение зависимостей
- Логирование
- Исключения