use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Method {
Get,
Put,
Post,
Delete,
}
impl Method {
pub fn as_str(self) -> &'static str {
match self {
Method::Get => "GET",
Method::Put => "PUT",
Method::Post => "POST",
Method::Delete => "DELETE",
}
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy)]
#[non_exhaustive]
pub struct Request<'a> {
pub method: Method,
pub path_and_query: &'a str,
pub if_match: Option<&'a str>,
pub if_none_match: Option<&'a str>,
pub authorization: Option<&'a str>,
pub body: Option<&'a [u8]>,
}
impl<'a> Request<'a> {
pub fn new(method: Method, path_and_query: &'a str) -> Request<'a> {
Request {
method,
path_and_query,
if_match: None,
if_none_match: None,
authorization: None,
body: None,
}
}
pub fn with_authorization(mut self, authorization: Option<&'a str>) -> Request<'a> {
self.authorization = authorization;
self
}
pub fn with_body(mut self, body: Option<&'a [u8]>) -> Request<'a> {
self.body = body;
self
}
pub fn with_if_match(mut self, if_match: Option<&'a str>) -> Request<'a> {
self.if_match = if_match;
self
}
pub fn with_if_none_match(mut self, if_none_match: Option<&'a str>) -> Request<'a> {
self.if_none_match = if_none_match;
self
}
}
impl std::fmt::Debug for Request<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Request")
.field("method", &self.method)
.field("path_and_query", &self.path_and_query)
.field("if_match", &self.if_match)
.field("if_none_match", &self.if_none_match)
.field("authorized", &self.authorization.is_some())
.field("body_len", &self.body.map(<[u8]>::len))
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Response {
pub status: u16,
pub etag: Option<String>,
pub expires_at: Option<String>,
pub body: Vec<u8>,
}
impl Response {
pub fn new(status: u16, body: impl Into<Vec<u8>>) -> Response {
Response {
status,
etag: None,
expires_at: None,
body: body.into(),
}
}
pub fn with_etag(mut self, etag: impl Into<String>) -> Response {
self.etag = Some(etag.into());
self
}
pub fn with_expires_at(mut self, expires_at: impl Into<String>) -> Response {
self.expires_at = Some(expires_at.into());
self
}
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Response")
.field("status", &self.status)
.field("etag", &self.etag)
.field("expires_at", &self.expires_at)
.field("body_len", &self.body.len())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct TransportError {
server: String,
message: String,
source: Option<Arc<dyn std::error::Error + Send + Sync>>,
}
impl TransportError {
pub fn new(server: impl Into<String>, message: impl Into<String>) -> TransportError {
TransportError {
server: server.into(),
message: message.into(),
source: None,
}
}
pub fn with_source(
server: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> TransportError {
TransportError {
server: server.into(),
message: source.to_string(),
source: Some(Arc::new(source)),
}
}
pub fn server(&self) -> &str {
&self.server
}
pub fn message(&self) -> &str {
&self.message
}
pub fn cause(&self) -> Option<Arc<dyn std::error::Error + Send + Sync>> {
self.source.clone()
}
}
impl std::fmt::Display for TransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "could not reach {}: {}", self.server, self.message)
}
}
impl std::error::Error for TransportError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_deref()
.map(|e| e as &(dyn std::error::Error + 'static))
}
}
pub trait Transport: Send + Sync {
fn send(&self, request: Request<'_>) -> Result<Response, TransportError>;
}
impl<T: Transport + ?Sized> Transport for Arc<T> {
fn send(&self, request: Request<'_>) -> Result<Response, TransportError> {
(**self).send(request)
}
}
impl<T: Transport + ?Sized> Transport for Box<T> {
fn send(&self, request: Request<'_>) -> Result<Response, TransportError> {
(**self).send(request)
}
}