Создание CLI на Rust с запросами к CoinMarketCap
Почему CLI остаётся важным инструментом
Командная строка (CLI) — лёгкий, быстрый и скриптуемый интерфейс для взаимодействия с системами, автоматизации и удалённого администрирования. CLI особенно полезен для разработчиков, SRE и инженеров автоматизации: оно легко интегрируется в конвейеры CI/CD, запускается на серверах без GUI и хорошо подходит для быстрой отладки.
Короткое определение: CLI — текстовая программа, управляемая через аргументы и стандартный ввод/вывод.
Почему Rust для CLI
- Надёжность памяти: компилятор Rust ловит многие ошибки на этапе компиляции.
- Высокая производительность и низкие накладные расходы.
- Экосистема: mature‑крейты для парсинга аргументов, работы с терминалом и сериализации.
- Хорошая поддержка асинхронности через Tokio и совместимость с reqwest.
Краткая подсказка: если нужен быстрый, безопасный и переносимый бинарник — Rust отличный выбор.
Основные варианты для построения CLI в Rust
Основные библиотеки для парсинга аргументов и взаимодействия с терминалом:
- Clap — полнофункциональная, широко используемая библиотека.
- StructOpt — удобная обёртка над Clap (часто уже объединена с Clap в новых версиях).
- Termion / Crossterm — для работы с выводом в терминал и управления курсором.
Противопоказание: для очень простых однофайловых утилит можно обойтись std::env::args(), но теряете удобства парсинга, валидирования и помощи пользователю.
Начало проекта: Cargo и зависимости
Создайте новый проект с помощью Cargo:
cargo new crypto_cliОткройте Cargo.toml и добавьте зависимости (версии из примера можно обновить):
[dependencies]
# Асинхронный рантайм
tokio = { version = "1.15", features = ["full"] }
# HTTP-клиент с поддержкой async и JSON
reqwest = { version = "0.11", features = ["json"] }
# Сериализация/десериализация
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# По желанию: clap = "3" для парсинга аргументовВажно: обновляйте версии в соответствии с текущими выпусками на crates.io.
Структура проекта и выделение модулей
Рекомендуемая простая структура:
- src/main.rs — точка входа и вызов CLI
- src/cli.rs — разбор аргументов и маршрутизация команд
- src/api.rs — логика HTTP‑запросов и десериализации
Создайте файлы:
touch src/api.rs src/cli.rsРазделение помогает тестировать модули отдельно и поддерживать код.
Вызов API CoinMarketCap с помощью Reqwest
CoinMarketCap предоставляет REST API с данными о котировках, исторических ценах и метаданных. Чтобы работать с приватными ключами, зарегистрируйтесь на их сайте и получите API‑ключ на странице разработчика.
Рекомендуемая практика: НЕ храните ключ в коде. Используйте переменные окружения или менеджер секретов.
Пример структур данных для десериализации ответа
Ниже — пример структур в Rust, адаптированных для serde. Этот код показывает, как можно описать ожидаемые поля ответа. В реальном мире структура ответа может быть динамической (словарь с id), поэтому в примерах мы также укажем альтернативы.
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct ApiResponse {
data: Data,
}
#[derive(Debug, Deserialize, Serialize)]
struct Data {
// Пример статической привязки для первых четырёх id
#[serde(rename = "1")]
crypto_1: Cryptocurrency,
#[serde(rename = "2")]
crypto_2: Cryptocurrency,
#[serde(rename = "3")]
crypto_3: Cryptocurrency,
#[serde(rename = "4")]
crypto_4: Cryptocurrency,
}
#[derive(Debug, Deserialize, Serialize)]
struct Cryptocurrency {
id: u32,
name: String,
symbol: String,
quote: Quote,
}
#[derive(Debug, Deserialize, Serialize)]
struct Quote {
USD: QuoteDetails,
}
#[derive(Debug, Deserialize, Serialize)]
struct QuoteDetails {
price: f64,
volume_24h: f64,
}Примечание: если API возвращает динамическое поле data: { “1”: {…}, “2”: {…} }, удобнее использовать HashMap
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
struct ApiResponseMap {
data: HashMap,
} Такой подход гибче, если набор id заранее неизвестен.
Асинхронный HTTP‑запрос с обработкой ответа
Ниже — скорректированный и безопасный пример запроса с использованием переменной окружения для ключа и методом .json() для автоматической десериализации.
use reqwest::Client;
use reqwest::Error;
use std::env;
pub async fn crypto() -> Result<(), Error> {
let client = Client::new();
let url = "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest";
let params = [
("id", "1,2,3,4"),
("convert", "USD"),
];
// Получаем API-ключ из переменной окружения CMC_API_KEY
let api_key = env::var("CMC_API_KEY").expect("Set CMC_API_KEY env variable");
let response = client
.get(url)
.header("X-CMC_PRO_API_KEY", api_key)
.query(¶ms)
.send()
.await?;
// Десериализуем JSON в структуру (требует serde в зависимостях)
let result: ApiResponse = response.json().await?;
println!("{:#?}", result);
Ok(())
}Объяснение: client.get(…).header(…).query(…).send().await? — отправляет запрос и возвращает ответ. response.json().await? автоматически десериализует тело ответа в указанный тип.
Важно: обрабатывать ошибки сетевого уровня и случаев, когда API вернёт код 4xx/5xx — в production‑коде добавьте проверку response.status() и подробную обработку ошибок.
Безопасное хранение API‑ключей
Рекомендации:
- Используйте переменные окружения (например, CMC_API_KEY) в окружениях разработки и CI.
- В production используйте менеджеры секретов (Vault, AWS Secrets Manager, GCP Secret Manager).
- Не коммитьте ключи в репозиторий и добавьте исключения в .gitignore.
Пример экспорта переменной в Unix‑окружении:
export CMC_API_KEY="ваш_api_ключ"В Windows PowerShell:
$env:CMC_API_KEY = "ваш_api_ключ"Получение аргументов командной строки
В простом варианте можно использовать std::env::args(). Для более мощного парсинга — clap.
Пример cli.rs с простым парсингом и вызовом функции crypto:
use std::env;
use crate::api::crypto;
pub async fn cli() {
let args: Vec = env::args().collect();
if args.len() > 1 && args[1] == "crypto" {
if let Err(e) = crypto().await {
eprintln!("Ошибка при запросе: {}", e);
}
} else {
println!("Invalid command. Usage: cargo run crypto");
}
} А точка входа в main.rs:
mod api;
mod cli;
use crate::cli::cli;
#[tokio::main]
async fn main() {
cli().await;
}Запуск в режиме разработки:
cargo run -- crypto(Обратите внимание: после двойного тире все параметры передаются в программу.)
Обработка ошибок и надёжность
- Проверяйте HTTP‑статусы: response.status().is_success().
- Обрабатывайте таймауты и сетевые ошибки: используйте конфигурацию клиента с .timeout(…).
- В production добавьте ретраи и экспоненциальную задержку при ошибках 5xx.
- Логируйте ошибки с контекстом, но не логи API‑ключи.
Тестирование и критерии приёмки
Критерии приёмки минимального функционала:
- Программа принимает команду crypto и возвращает десериализуемый объект.
- При отсутствии ключа выводится понятная ошибка.
- Обработка ошибок сети и API реализована (статус != 200 => сообщение об ошибке).
Тесты:
- Unit test для функций парсинга ответа с фиктивным JSON.
- Интеграционный тест с mock‑сервером (wiremock, httpmock).
Альтернативные подходы
- Синхронный клиент: если проект не требует concurrency, можно использовать синхронные библиотеки — меньше сложности, но теряется асинхронная масштабируемость.
- GraphQL/WS: если API поддерживает WebSocket или GraphQL, можно получать обновления в реальном времени.
- Использовать готовые SDK от провайдера (если доступны).
Шаблоны и чек‑листы для ролей
Разработчик:
- Настроить Cargo.toml и зависимости.
- Реализовать api.rs и cli.rs.
- Добавить обработку ошибок и тесты.
- Добавить документацию использования.
Оператор/DevOps:
- Настроить переменные окружения в CI/CD.
- Настроить мониторинг и алерты.
- Регулярно обновлять зависимости и проверять уязвимости.
QA:
- Написать unit/integration тесты.
- Проверить поведение при недоступности API и при таймаутах.
Сниппет: частые команды Cargo
cargo build # собрать проект
cargo run -- crypto # запустить с аргументом
cargo test # запустить тесты
cargo fmt # отформатировать код
cargo clippy # запустить линтерМини‑методология разработки CLI на Rust (быстрый план из 5 шагов)
- Спроектировать интерфейс команд и аргументов (что принимает пользователь).
- Определить структуру проекта и API‑слой.
- Настроить безопасность (секреты, переменные окружения).
- Реализовать функционал и добавить логирование/обработку ошибок.
- Тесты, CI и деплой как single‑binary в target окружение.
Decision tree для выбора подхода (Mermaid)
flowchart TD
A[Нужен CLI?] -->|Да| B{Требуется асинхронность}
B -->|Да| C[Использовать Tokio + reqwest]
B -->|Нет| D[Использовать std + синхронный клиент]
C --> E{Нужна сложная обработка аргументов?}
E -->|Да| F[Clap]
E -->|Нет| G[std::env::args'']
D --> GСовместимость, миграция и расширение
- Если в будущем планируете GUI или веб, выделяйте бизнес‑логику в отдельные модули, чтобы её можно было переиспользовать в Actix/Rocket.
- Обновление зависимостей: проверьте мажорные версии Tokio и Reqwest — они могут содержать несовместимые изменения.
Безопасность и приватность
- Не логируйте содержимое API‑ключей или чувствительные поля ответа.
- Для пользовательских данных проверьте требования GDPR/местного законодательства перед передачей данных третьим сторонам.
Когда подход с Rust + reqwest может не сработать
- Если вам нужен быстрый прототип с минимальными зависимостями — скрипт на Python/Node иногда быстрее в разработке.
- Если важно, чтобы приложение работало на старых системах без возможности установки бинарников — придётся учитывать целевую платформу.
Заключение
Rust даёт сильные гарантии безопасности и эффективности при разработке CLI. В связке с Tokio, Reqwest и Serde вы получаете современный стек для работы с сетевыми API. Важно проектировать безопасное хранение ключей, тщательно обрабатывать ошибки и писать тесты.
В следующих шагах: замените статическое перечисление id на динамическую модель (HashMap), подключите clap для удобного парсинга аргументов и вынесите конфигурацию в отдельный модуль.
Важное: перед публикацией убедитесь, что в бинарник не попал ваш API‑ключ и что в репозитории нет секретов.
Краткое резюме:
- Создали базовую структуру CLI на Rust.
- Сделали асинхронный запрос к CoinMarketCap и десериализовали ответ.
- Добавили рекомендации по безопасности, тестированию и расширению.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone