Compare commits
No commits in common. "9de12169a6715a0898f3b8280b5f2697826c11bc" and "de4903f1e60cee38fd1bc4e7d20f8d4295821b1a" have entirely different histories.
9de12169a6
...
de4903f1e6
4 changed files with 12 additions and 63 deletions
|
@ -1,5 +1,3 @@
|
||||||
//! Builder for Client
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -14,7 +12,6 @@ use tokio_rustls::rustls::{
|
||||||
SupportedProtocolVersion,
|
SupportedProtocolVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Builder for creating configured [`Client`] instance
|
|
||||||
pub struct ClientBuilder {
|
pub struct ClientBuilder {
|
||||||
root_certs: rustls::RootCertStore,
|
root_certs: rustls::RootCertStore,
|
||||||
ss_verifier: Option<Box<dyn SelfsignedCertVerifier>>,
|
ss_verifier: Option<Box<dyn SelfsignedCertVerifier>>,
|
||||||
|
|
|
@ -99,7 +99,7 @@ impl Client {
|
||||||
/// Check the inner error if you need to determine what exactly happened
|
/// Check the inner error if you need to determine what exactly happened
|
||||||
/// - [`LibError::StatusOutOfRange`] means that a server returned an incorrect status code
|
/// - [`LibError::StatusOutOfRange`] means that a server returned an incorrect status code
|
||||||
/// (less than 10 or greater than 69).
|
/// (less than 10 or greater than 69).
|
||||||
/// - [`LibError::DataNotUtf8`] is returned when metadata (the text after a status code)
|
/// - [`LibError::MessageNotUtf8`] is returned when metadata (the text after the status code)
|
||||||
/// is not in UTF-8 and cannot be converted to string without errors.
|
/// is not in UTF-8 and cannot be converted to string without errors.
|
||||||
pub async fn request_with_host(
|
pub async fn request_with_host(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
//! Client-side response structure
|
|
||||||
|
|
||||||
use crate::{status::Status, LibError, ReplyType};
|
use crate::{status::Status, LibError, ReplyType};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
@ -7,8 +5,6 @@ use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
type BodyStream = tokio::io::BufReader<tokio_rustls::client::TlsStream<tokio::net::TcpStream>>;
|
type BodyStream = tokio::io::BufReader<tokio_rustls::client::TlsStream<tokio::net::TcpStream>>;
|
||||||
|
|
||||||
/// Client-side response structure wrapping a [`Status`],
|
|
||||||
/// a metadata string and a TLS stream
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
status: Status,
|
status: Status,
|
||||||
|
@ -25,25 +21,11 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if status code is 2x (success).
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_ok(&self) -> bool {
|
pub fn is_ok(&self) -> bool {
|
||||||
self.status.reply_type() == ReplyType::Success
|
self.status.reply_type() == ReplyType::Success
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `Ok(self)` if status code is 2x, otherwise `Err(self)`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// match client.request("gemini://dc09.ru").await?.ensure_ok() {
|
|
||||||
/// Ok(resp) => {
|
|
||||||
/// println!("{}", resp.text().await?);
|
|
||||||
/// }
|
|
||||||
/// Err(resp) => {
|
|
||||||
/// println!("{}", resp.message());
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn ensure_ok(self) -> Result<Self, Self> {
|
pub fn ensure_ok(self) -> Result<Self, Self> {
|
||||||
if self.status.reply_type() == ReplyType::Success {
|
if self.status.reply_type() == ReplyType::Success {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
@ -52,50 +34,28 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the response status code as [`Status`].
|
|
||||||
pub fn status(&self) -> Status {
|
pub fn status(&self) -> Status {
|
||||||
self.status
|
self.status
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the response metadata -- the text after a status code + space.
|
|
||||||
/// - In 1x responses (input), it contains an input prompt message.
|
|
||||||
/// - In 2x responses (success), it contains a MIME type of the body.
|
|
||||||
/// - In 3x responses (redirect), it contains a URL.
|
|
||||||
/// - In 4x, 5x (fail), 6x (auth), it contains an error message.
|
|
||||||
pub fn message(&self) -> &str {
|
pub fn message(&self) -> &str {
|
||||||
&self.message
|
&self.message
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the response body MIME type by parsing the metadata field.
|
|
||||||
/// If you call this method on a non-2x response, you'll get a parse error
|
|
||||||
/// ([`LibError::InvalidMime`]).
|
|
||||||
/// It's strongly recommended to check the status code first
|
|
||||||
/// (most handy is [`Response::is_ok()`]).
|
|
||||||
pub fn mime(&self) -> Result<mime::Mime, LibError> {
|
pub fn mime(&self) -> Result<mime::Mime, LibError> {
|
||||||
self.message.parse().map_err(LibError::InvalidMime)
|
self.message.parse().map_err(LibError::InvalidMime)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the wrapped TLS stream as `&mut`.
|
|
||||||
///
|
|
||||||
/// # Reminder
|
|
||||||
/// You can read data from one stream only once,
|
|
||||||
/// so calling `.bytes()` after `.stream().read_to_end(…)`
|
|
||||||
/// (or `.bytes()` twice, or `.text()` after `.bytes()` and vice versa)
|
|
||||||
/// on the same response will result in empty output.
|
|
||||||
pub fn stream(&mut self) -> &mut BodyStream {
|
pub fn stream(&mut self) -> &mut BodyStream {
|
||||||
&mut self.stream
|
&mut self.stream
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the whole response body and return as [`Bytes`].
|
|
||||||
/// See also [the note](#reminder) to `stream()`.
|
|
||||||
pub async fn bytes(&mut self) -> Result<Bytes, LibError> {
|
pub async fn bytes(&mut self) -> Result<Bytes, LibError> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
self.stream.read_to_end(&mut buf).await?;
|
self.stream.read_to_end(&mut buf).await?;
|
||||||
Ok(Bytes::from(buf))
|
Ok(Bytes::from(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the whole response body as a UTF-8 [`String`].
|
|
||||||
/// See also [the note](#reminder) to `stream()`.
|
|
||||||
pub async fn text(&mut self) -> Result<String, LibError> {
|
pub async fn text(&mut self) -> Result<String, LibError> {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
self.stream.read_to_string(&mut buf).await?;
|
self.stream.read_to_string(&mut buf).await?;
|
||||||
|
|
30
src/error.rs
30
src/error.rs
|
@ -1,19 +1,10 @@
|
||||||
//! Library error structures and enums
|
|
||||||
|
|
||||||
/// Main error structure, also a wrapper for everything else
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LibError {
|
pub enum LibError {
|
||||||
/// General I/O error
|
|
||||||
// TODO: separate somehow
|
|
||||||
IoError(std::io::Error),
|
IoError(std::io::Error),
|
||||||
/// URL parse or check error
|
|
||||||
InvalidUrlError(InvalidUrl),
|
InvalidUrlError(InvalidUrl),
|
||||||
/// Response status code is out of [10; 69] range
|
|
||||||
StatusOutOfRange(u8),
|
StatusOutOfRange(u8),
|
||||||
/// Response metadata or content cannot be parsed
|
MessageNotUtf8(std::string::FromUtf8Error),
|
||||||
/// as a UTF-8 string without errors
|
BodyNotUtf8(std::str::Utf8Error),
|
||||||
DataNotUtf8(std::string::FromUtf8Error),
|
|
||||||
/// Provided string is not a valid MIME type
|
|
||||||
InvalidMime(mime::FromStrError),
|
InvalidMime(mime::FromStrError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +39,14 @@ impl LibError {
|
||||||
impl From<std::string::FromUtf8Error> for LibError {
|
impl From<std::string::FromUtf8Error> for LibError {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||||
Self::DataNotUtf8(err)
|
Self::MessageNotUtf8(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::str::Utf8Error> for LibError {
|
||||||
|
#[inline]
|
||||||
|
fn from(err: std::str::Utf8Error) -> Self {
|
||||||
|
Self::BodyNotUtf8(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,17 +57,11 @@ impl From<mime::FromStrError> for LibError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// URL parse or check error
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InvalidUrl {
|
pub enum InvalidUrl {
|
||||||
/// Provided string cannot be parsed as a valid URL with [`url::Url`]
|
|
||||||
ParseError(url::ParseError),
|
ParseError(url::ParseError),
|
||||||
/// URL scheme is not `gemini://`
|
|
||||||
SchemeNotGemini,
|
SchemeNotGemini,
|
||||||
/// URL contains userinfo -- `user:pswd@` --
|
|
||||||
/// which is forbidden by Gemini spec
|
|
||||||
UserinfoPresent,
|
UserinfoPresent,
|
||||||
/// Could not extract host from the URL or convert host and port into
|
NoHostFound,
|
||||||
/// [`std::net::SocketAddr`] or [`crate::certs::ServerName`]
|
|
||||||
ConvertError,
|
ConvertError,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue