rust-mc-status 3.0.0

High-performance asynchronous Rust library for querying Minecraft server status (Java & Bedrock)
Documentation
<div align="center">
  <img src="assets/logo.svg" alt="rust-mc-status logo" width="160" height="160">
  <h1>rust-mc-status</h1>
  <p>
    <strong>Высокопроизводительный асинхронный клиент статуса серверов Minecraft для Rust — Java и Bedrock Edition.</strong>
  </p>
  <p>

<!-- prettier-ignore-start -->

[![crates.io](https://img.shields.io/crates/v/rust-mc-status?label=latest)](https://crates.io/crates/rust-mc-status)
[![Documentation](https://docs.rs/rust-mc-status/badge.svg?version=3.0.0)](https://docs.rs/rust-mc-status/3.0.0)
![MSRV](https://img.shields.io/badge/rustc-1.85+-ab6000.svg)
![MIT licensed](https://img.shields.io/crates/l/rust-mc-status.svg)
[![Dependency Status](https://deps.rs/crate/rust-mc-status/3.0.0/status.svg)](https://deps.rs/crate/rust-mc-status/3.0.0)
<br />
![downloads](https://img.shields.io/crates/d/rust-mc-status.svg)

<!-- prettier-ignore-end -->

  </p>
</div>

---

**Документация**: [https://docs.rs/rust-mc-status](https://docs.rs/rust-mc-status/3.0.0)

**Исходный код**: [https://github.com/NameOfShadow/rust-mc-status](https://github.com/NameOfShadow/rust-mc-status)

---

## 🎉 v3.0.0 — выпуск 2026-07-01

Крупный рефакторинг с акцентом на эргономику, производительность и расширяемость. **Ломающие изменения** относительно 2.x — см. [руководство по миграции](CHANGELOG.md#migration-guide-2x--300).

**Главное:**

- **Builder API**`McClient::builder().timeout(...).response_cache(...).build()` вместо разрозненных `with_*`.
- **Fluent-пинги**`client.java("addr").timeout(3s).await?` и `.is_online()` на каждый запрос.
- **Response-кэш с дедупликацией in-flight запросов** — одновременные пинги одного сервера сходятся в один сетевой запрос; попадание в кэш — ~0 мс.
- **Двухуровневая иерархия ошибок**`McError::Network(NetworkError::Timeout)` и т.п. для точного матчинга и retry-политик.
- **Поддержка прокси** *(feature = "proxy")* — SOCKS5 (опционально с UDP для Bedrock) и HTTP CONNECT, с авторизацией.
- **Tower-интеграция** *(feature = "tower")*`McService` и `McRetryPolicy` встраиваются в экосистему Tower.
- **Слоистая структура `src/`**`client/`, `core/`, `models/`, `protocol/`, `proxy/`, `status/` для удобной навигации.
- **Без оверхеда по умолчанию** — без опциональных фич не подтягиваются tower, pin-project и tokio-socks.
- **149 тестов в 8 интеграционных файлах** — парсинг DNS, декодеры Bedrock/Java, иерархия ошибок, response-кэш, конфиг прокси, форматирование MOTD.

Полные релиз-ноты и руководство по миграции — в [CHANGELOG.md](CHANGELOG.md).

---

## Возможности

- **Поддержка двух протоколов** — пинг Java Edition (порт `25565`) и Bedrock Edition (порт `19132`)
- **Удобный Builder API**`client.java("addr").timeout(3s).await?` с таймаутом на уровне запроса
- **Типизированные обёртки**`JavaServerStatus` и `BedrockServerStatus` с методами для каждой редакции
- **Фасадные функции**`ping_java("addr").await?` без создания клиента
- **Tower Middleware** *(опциональная фича)* — rate limiting, retry, буферизация, трейсинг через экосистему Tower
- **Async/Await** — построена на Tokio для неблокирующих операций и высокой параллельности
- **Массовые запросы** — параллельная проверка множества серверов с настраиваемым лимитом
- **LRU DNS-кэш** — DNS и SRV-записи кэшируются с автоматическим вытеснением (по умолчанию 1024 записи)
- **Поддержка SRV-записей** — автоматический SRV-запрос для Java-серверов, как в официальном клиенте
- **JSON-сериализация** — все типы статуса реализуют `serde::Serialize`; favicon и raw data исключены автоматически
- **Форматирование MOTD**`motd_clean()` убирает `§`-коды; `truncate_str()` безопасно обрезает Unicode
- **Zero-Panic дизайн** — все данные от сервера валидируются перед использованием
- **SmallVec оптимизация** — список игроков, плагинов и модов хранится на стеке для малых размеров
- **Zero-cost без Tower** — стандартная сборка не тянет tower, async-trait и pin-project

## Установка

```toml
[dependencies]
rust-mc-status = "3.0.0"
tokio = { version = "*", features = ["full"] }
```

Для Tower middleware (rate limiting, retry, буферизация, трейсинг):

```toml
rust-mc-status = { version = "3.0.0", features = ["tower"] }
```

## Быстрый старт

### Одна строка — без клиента

```rust
use rust_mc_status::{ping_java, ping_bedrock, StatusExt};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let status = ping_java("mc.hypixel.net").await?;
    println!("{}", status);                    // "mc.hypixel.net [42 ms] 32000/200000 — Hypixel Network"
    println!("{}", status.display_players());  // "32000/200000"
    println!("{}", status.motd_clean());       // MOTD без §-кодов
    Ok(())
}
```

### Клиент с настройками

```rust
use rust_mc_status::{McClient, StatusExt};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = McClient::new()
        .with_timeout(Duration::from_secs(10))
        .with_max_parallel(20)
        .with_cache_size(2048);

    // Типизированный статус Java
    let java = client.java("mc.hypixel.net").await?;
    println!("Версия   : {}", java.version());
    println!("Игроки   : {}", java.display_players());
    println!("Задержка : {:.0} мс", java.latency_ms());
    println!("MOTD     : {}", java.motd_clean());

    // Таймаут на уровне запроса — конфиг клиента не меняется
    let fast = client.java("mc.hypixel.net")
        .timeout(Duration::from_secs(3))
        .await?;
    println!("Быстрая проверка: {} мс", fast.latency_ms() as u32);

    // Типизированный статус Bedrock
    let bedrock = client.bedrock("geo.hivebedrock.network:19132").await?;
    println!("Редакция : {}", bedrock.edition());
    println!("Игроки   : {}", bedrock.display_players());

    Ok(())
}
```

### Массовые запросы

```rust
use rust_mc_status::{McClient, ServerEdition, ServerInfo};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = McClient::new();
    let servers = vec![
        ServerInfo { address: "mc.hypixel.net".into(),                edition: ServerEdition::Java },
        ServerInfo { address: "geo.hivebedrock.network:19132".into(), edition: ServerEdition::Bedrock },
    ];

    for (server, result) in client.ping_many(&servers).await {
        match result {
            Ok(s)  => println!("{}: онлайн ({:.0} мс)", server.address, s.latency),
            Err(e) => println!("{}: {}", server.address, e),
        }
    }
    Ok(())
}
```

### JSON-сериализация

```rust
let status = client.java("mc.hypixel.net").await?;
let json = serde_json::to_string(&status)?;         // компактный
let json = serde_json::to_string_pretty(&status)?;  // форматированный
// favicon и raw_data исключены автоматически
```

### Выбор редакции в рантайме

```rust
let edition: ServerEdition = "java".parse()?;

// Полный статус
let status = client.server("mc.hypixel.net", edition).await?;

// Только проверка доступности — без парсинга статуса
let online = client.server("mc.hypixel.net", edition)
    .timeout(Duration::from_secs(3))
    .is_online()
    .await;
```

### Кастомные типы адресов

```rust
struct ServerId(String);
impl From<ServerId> for String { fn from(s: ServerId) -> Self { s.0 } }

let status = client.java(ServerId("mc.hypixel.net".into())).await?;
```

## Tower Middleware

Подключается через `features = ["tower"]`. Преобразует `McClient` в стандартный `tower::Service`, совместимый со всей экосистемой Tower — rate limiting, retry, буферизация, `tower-http` трейсинг, любой `tower::Layer`.

```rust
use tower::{ServiceBuilder, ServiceExt};
use rust_mc_status::{McClient, McRetryPolicy, PingRequestTower};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut svc = ServiceBuilder::new()
        .buffer(64)                                      // Clone + параллельность
        .rate_limit(100, Duration::from_secs(1))         // 100 запросов/сек
        .retry(McRetryPolicy::new(3))                    // 3 попытки при таймауте/ошибке соединения
        .service(McClient::new().into_service());

    svc.ready().await?;
    let resp = svc.call(PingRequestTower::java("mc.hypixel.net")).await?;
    println!("{:.0} мс", resp.status.latency);
    Ok(())
}
```

### Кастомный Tower Layer

Реализуй `tower::Layer` для логирования, метрик или любого другого middleware:

```rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tower::{Layer, Service};
use rust_mc_status::{McError, PingRequestTower, PingResponseTower};

#[derive(Clone)]
struct LogLayer;

impl<S> Layer<S> for LogLayer {
    type Service = LogService<S>;
    fn layer(&self, inner: S) -> LogService<S> { LogService { inner } }
}

#[derive(Clone)]
struct LogService<S> { inner: S }

impl<S> Service<PingRequestTower> for LogService<S>
where
    S: Service<PingRequestTower, Response = PingResponseTower, Error = McError> + Send,
    S::Future: Send + 'static,
{
    type Response = PingResponseTower;
    type Error    = McError;
    type Future   = Pin<Box<dyn Future<Output = Result<PingResponseTower, McError>> + Send>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), McError>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: PingRequestTower) -> Self::Future {
        println!("→ {}", req.address);
        let fut = self.inner.call(req);
        Box::pin(async move {
            let resp = fut.await?;
            println!("{:.0} мс", resp.status.latency);
            Ok(resp)
        })
    }
}

// Использование
let mut svc = ServiceBuilder::new()
    .layer(LogLayer)
    .service(McClient::new().into_service());
```

## Поддержка SRV-записей

При пинге Java-сервера **без явного порта** библиотека запрашивает `_minecraft._tcp.{hostname}` — точно так же, как официальный клиент Minecraft. Результаты кэшируются на 5 минут.

```rust
// SRV-запрос выполняется автоматически
let status = client.java("hypixel.net").await?;

// Явный порт — SRV-запрос пропускается
let status = client.java("mc.hypixel.net:25565").await?;
```

## Форматирование MOTD

```rust
use rust_mc_status::strip_formatting;

let raw = "§aHypixel §c[1.8/1.21]";
println!("{}", strip_formatting(raw)); // "Hypixel [1.8/1.21]"

// Или через метод обёртки
let status = client.java("mc.hypixel.net").await?;
println!("{}", status.motd_clean()); // тот же результат
```

## Примеры

| Пример | Описание |
|---|---|
| `basic_usage` | Базовый API: фасад, типизированный статус, таймаут на запрос, сериализация |
| `advanced_usage` | Массовые запросы, плагины, моды, сохранение favicon |
| `cache_management` | Статистика LRU-кэша, cold/warm замеры, `clear_caches()` |
| `tower_usage` | Tower middleware: rate limiting, retry, buffer, кастомный Layer |
| `srv_lookup_example` | Поведение SRV-записей |
| `performance_test` | Бенчмарк (запускать с `--release`) |

```bash
cargo run --example basic_usage
cargo run --example cache_management
cargo run --example tower_usage --features tower
cargo run --example performance_test --release
```

## Справочник API

### Builder-методы `McClient`

| Метод | Описание |
|---|---|
| `new()` | Настройки по умолчанию (10 с таймаут, 10 параллельных, 1024 кэш) |
| `with_timeout(Duration)` | Глобальный таймаут запроса |
| `with_max_parallel(usize)` | Максимум параллельных пингов в `ping_many` |
| `with_cache_size(usize)` | Ёмкость LRU-кэша для DNS и SRV |
| `into_service()` *(tower)* | Преобразовать в `McService` для Tower middleware |

### Методы пинга

| Метод | Возвращает | Примечание |
|---|---|---|
| `client.java(addr)` | `JavaPingBuilder` | Поддерживает `.timeout(d).await` |
| `client.bedrock(addr)` | `BedrockPingBuilder` | Поддерживает `.timeout(d).await` |
| `client.server(addr, edition)` | `ServerPingBuilder` | Рантайм-редакция; `.is_online().await` |
| `client.ping_many(&[ServerInfo])` | `Vec<(ServerInfo, Result<ServerStatus>)>` | Параллельный батч |
| `ping_java(addr)` | `Result<JavaServerStatus>` | Глобальный клиент, без настройки |
| `ping_bedrock(addr)` | `Result<BedrockServerStatus>` | Глобальный клиент, без настройки |

### Tower-типы *(feature = "tower")*

| Тип | Описание |
|---|---|
| `McService` | `tower::Service<PingRequestTower>` — обёртка над `McClient` |
| `McRetryPolicy` | Retry при `Timeout` / `ConnectionError`; настраиваемое число попыток |
| `PingRequestTower` | Тип запроса с конструкторами `.java(addr)` / `.bedrock(addr)` |
| `PingResponseTower` | Ответ, содержащий `status: ServerStatus` |

### Методы `JavaServerStatus`

`motd()`, `motd_clean()`, `version()`, `players_online()`, `players_max()`, `display_players()`, `favicon()`, `save_favicon(path)`, `ip()`, `port()`, `hostname()`, `latency_ms()`, `is_online()`, `raw()`

### Методы `BedrockServerStatus`

`motd()`, `motd_clean()`, `motd2()`, `motd2_clean()`, `edition()`, `version()`, `players_online()`, `players_max()`, `display_players()`, `game_mode()`, `ip()`, `port()`, `hostname()`, `latency_ms()`, `is_online()`, `raw()`

### Утилиты

| Функция | Описание |
|---|---|
| `strip_formatting(s)` | Убрать `§X`-коды Minecraft, схлопнуть пробелы |
| `truncate_str(s, n)` | Обрезать до `n` Unicode-символов (без паник) |

### Методы кэша (все `async`)

`cache_stats().await`, `clear_caches().await`

## Обработка ошибок

```rust
use rust_mc_status::McError;

match client.java("server.com").await {
    Ok(s)                              => println!("онлайн: {}", s.display_players()),
    Err(McError::Timeout)              => println!("таймаут"),
    Err(McError::DnsError(msg))        => println!("DNS: {}", msg),
    Err(McError::ConnectionError(msg)) => println!("соединение: {}", msg),
    Err(e)                             => println!("ошибка: {}", e),
}
```

## Производительность

- DNS и SRV-записи кэшируются на 5 минут с LRU-вытеснением — без утечек памяти
- `tokio::sync::RwLock` позволяет одновременное чтение с минимальной конкуренцией
- `SmallVec` для списка игроков (≤ 12), плагинов (≤ 8) и модов (≤ 8) — хранение на стеке
- `.timeout()` на уровне запроса снижает хвостовую латентность без изменения конфига клиента
- `ping_bedrock` использует UDP; `ping_java` использует TCP с `TCP_NODELAY`
- Стандартная сборка не включает tower/async-trait/pin-project — только с `features = ["tower"]`

## Лицензия

MIT — см. [LICENSE](LICENSE).

## Список изменений

См. [CHANGELOG.md](CHANGELOG.md).

## Версия

Текущая версия: **3.0.0** (выпущена 2026-07-01) — см. [CHANGELOG.md](CHANGELOG.md) для полной истории и руководства по миграции.