Как написать библиотеку на Rust? Реальный пример
Сегодня я покажу тебе как написать свою первую библиотеку, которая может быть реально нужна и будет полезна для твоего последывающего обучения как работать с API сервисов
Задумка
Любая библиотека начинается с задумки - как решить поставленную проблему.
Изучая различия между IPv4 и IPv6 я столкнулся с главным преимуществом IPv6 для построения децентрализованных компьютерных систем - отсутствие ограничений NAT.
Но как же получить собственный IPv6 адрес программным способом? Казалось бы, все сервисы выдают тебе только IPv4 адрес. Поэтому, я решил найти похожий, но с IPv6 и нашел https://wtfismyip.com
К счастью у них оказалась возможность получения информации собственного IPv6 адреса и краткой информации о нем в формате JSON и обычного текста(текстом только IPv6 адрес)
Вдобавок, я решил, что сделаю возможность использовать её и асинхронно и синхронно
Приступаем к написанию
Для начала надо создать проект с помощью cargo и сразу добавить все необходимые зависимости
cargo new mynewlib --lib && cd mynewlib
cargo add serde --features derive
cargo add reqwest --features blocking
cargo add serde_json
Теперь открываем папочку с проектом в любимом редакторе кода и начинаем писать код
Код
Создадим два файла src/async.rs и src/sync.rs для асинхронных и синхронных реализаций соответственно
В src/lib.rs укажем эти два файла как части библиотеки благодаря оператору mod и создадим структуру IpInfo, которую мы будем использовать для получения данных в формате JSON
pub mod sync;
mod r#async;
pub use r#async::*;
use serde::Deserialize;
use std::net::Ipv6Addr;
#[derive(Deserialize, Debug, Clone)]
pub struct IpInfo {
#[serde(rename = "YourFuckingIPAddress")]
pub ip_address: Ipv6Addr,
#[serde(rename = "YourFuckingLocation")]
pub location: String,
#[serde(rename = "YourFuckingHostname")]
pub hostname: Ipv6Addr,
#[serde(rename = "YourFuckingISP")]
pub isp: String,
#[serde(rename = "YourFuckingTorExit")]
pub tor_exit: bool,
#[serde(rename = "YourFuckingCity")]
pub city: String,
#[serde(rename = "YourFuckingCountry")]
pub country: String,
#[serde(rename = "YourFuckingCountryCode")]
pub country_code: String
}
Строкой pub use sync; мы определяем, что модуль(в данном случае файл) src/sync.rs публичный, то есть пользователь, использующий библиотеку в своём проекте может использовать его, а не только сама библиотека
Следующие две строки определяют, что всё содержимое модуля src/async.rs должно считаться, как содержимое корня библиотеки, то есть src/lib.rs. Для чего это сделано? Асинхронная реализация зачастую более востребована, поэтому пусть она будет реализацией "по умолчанию".
Используем #[serde(rename)], чтобы поля в нашей структуре назывались коротко и локанично, но при этом парсер JSON на основе serde мог понять, что например поле ip_address надо заполнить информацией из YourFuckingIPAddress
Префикс r# в данном случае используется для того, чтобы компилятор воспринимал слово async за название модуля, а не https://rust-lang.github.io/async-book/03_async_await/01_chapter.html. src/sync.rs
Используем https://docs.rs/reqwest/latest/reqwest/blocking/index.html модуль reqwest::blocking для синхронной реализации.
Функция get_ip_info возвращает "результат" - определённую нами структуру IpInfo или ошибку библиотеки https://docs.rs/reqwest/latest/reqwest/. Таким образом, мы даём возможность пользователю самому обработать ошибку.
Функция get_ip тоже возвращает "результат", но вместо IpInfo возвращает https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html (тип в стандартной библиотеке, для обработки IPv6 адресов) при успешном выполнении запроса. Функция strip_suffix используется, чтобы убрать переход на новую строку, потому что иначе получается ошибка при парсинге IPv6 адреса.
use crate::IpInfo;
use std::net::Ipv6Addr;
pub fn get_ip_info() -> Result<IpInfo, reqwest::Error> {
let body = reqwest::blocking::get("https://wtfismyip.com/json")?.text()?;
Ok(serde_json::from_str(&body).unwrap())
}
pub fn get_ip() -> Result<Ipv6Addr, reqwest::Error> {
let body = reqwest::blocking::get("https://wtfismyip.com/text")?.text()?
.strip_suffix("\n").unwrap()
.parse().unwrap();
Ok(body)
}
В асинхронной версии всё абсолютно тоже самое, за исключением некоторых моментов.
Здесь мы уже можем увидеть что используются ключевые слова async/.await, а вместо https://docs.rs/reqwest/latest/reqwest/blocking/fn.get.html используется асинхронный https://docs.rs/reqwest/latest/reqwest/fn.get.html
use crate::IpInfo;
use std::net::Ipv6Addr;
pub async fn get_ip_info() -> Result<IpInfo, reqwest::Error> {
let body = reqwest::get("https://wtfismyip.com/json").await?.text().await?;
Ok(serde_json::from_str(&body).unwrap())
}
pub async fn get_ip() -> Result<Ipv6Addr, reqwest::Error> {
let body = reqwest::get("https://wtfismyip.com/text").await?.text().await?
.strip_suffix("\n").unwrap()
.parse().unwrap();
Ok(body)
}
В будущем я планирую добавить поддержку нескольких других http-клиентов для этой библиотеки, которые вы сами сможете выбрать благодаря https://doc.rust-lang.org/cargo/reference/features.html. Все изменения можно будет отследить https://vipadmin.club/bot-redirect?https://endway.org/threads/1984/