pear_client 0.1.1

PEAR and PECL client
Documentation
use crate::context::{GetRef, PearUrl};
use crate::url_util::UrlExt;
use bytes::Bytes;
use core::task::{Context, Poll};
use futures::future::BoxFuture;
use http::{Method, Request, Response,};
use http_body::Body;
use http_body_util::{BodyExt, Full};
use std::error::Error as StdError;
use compact_str::format_compact;
use tower_service::Service;
use crate::common::package::{PackageInfo, PackageListing};
use crate::common::release::{Release, ReleaseListing};
use crate::query::get_package_info::GetPackageInfoQuery;
use crate::query::get_package_list::GetPackageListQuery;
use crate::query::get_release::GetReleaseQuery;
use crate::query::get_release_list::GetReleaseListQuery;

pub struct HttpPearClient<TyInner> {
  inner: TyInner,
}

impl<TyInner> HttpPearClient<TyInner> {
  pub fn new(inner: TyInner) -> Self {
    Self { inner }
  }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
pub enum HttpPearClientError {
  #[error("failed to poll ready status: {0}")]
  PollReady(String),
  #[error("failed to send request: {0}")]
  Send(String),
  #[error("failed to receive response: {0}")]
  Receive(String),
  #[error("failed to parse response: {0}")]
  ResponseFormat(String, Bytes),
  #[error("operation is forbidden for provided auth")]
  Forbidden,
  #[error("resource already exists")]
  Conflict,
  #[error("resource not found")]
  NotFound,
  #[error("unexpected error: {0}")]
  Other(String),
}

impl<'req, Cx, TyInner, TyBody> Service<&'req GetPackageListQuery<Cx>> for HttpPearClient<TyInner>
where
  Cx: GetRef<PearUrl>,
  TyInner: Service<Request<Full<Bytes>>, Response = Response<TyBody>> + 'req,
  TyInner::Error: StdError,
  TyInner::Future: Send,
  TyBody: Body + Send,
  TyBody::Data: Send,
  TyBody::Error: StdError,
{
  type Response = PackageListing;
  type Error = HttpPearClientError;
  type Future = BoxFuture<'req, Result<Self::Response, Self::Error>>;

  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    self
      .inner
      .poll_ready(cx)
      .map_err(|e| HttpPearClientError::PollReady(format!("{e:?}")))
  }

  fn call(&mut self, req: &'req GetPackageListQuery<Cx>) -> Self::Future {
    let url = req.context.get_ref().url_join(["p", "packages.xml"]);

    let req = Request::builder()
      .method(Method::GET)
      .uri(url.as_str())
      .body(Full::new(Bytes::new()))
      .unwrap();
    let res = self.inner.call(req);
    Box::pin(async move {
      let res: Response<TyBody> = res.await.map_err(|e| HttpPearClientError::Send(format!("{e:?}")))?;
      let body = res
        .into_body()
        .collect()
        .await
        .map_err(|e| HttpPearClientError::Receive(format!("{e:?}")))?;
      let body: Bytes = body.to_bytes();
      let result = PackageListing::from_xml(body.as_ref());
      Ok(result)
    })
  }
}

impl<'req, Cx, TyInner, TyBody> Service<&'req GetReleaseListQuery<Cx>> for HttpPearClient<TyInner>
where
  Cx: GetRef<PearUrl>,
  TyInner: Service<Request<Full<Bytes>>, Response = Response<TyBody>> + 'req,
  TyInner::Error: StdError,
  TyInner::Future: Send,
  TyBody: Body + Send,
  TyBody::Data: Send,
  TyBody::Error: StdError,
{
  type Response = ReleaseListing;
  type Error = HttpPearClientError;
  type Future = BoxFuture<'req, Result<Self::Response, Self::Error>>;

  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    self
      .inner
      .poll_ready(cx)
      .map_err(|e| HttpPearClientError::PollReady(format!("{e:?}")))
  }

  fn call(&mut self, req: &'req GetReleaseListQuery<Cx>) -> Self::Future {
    let url = req.context.get_ref().url_join(["r", req.package.as_str(), "allreleases.xml"]);

    let req = Request::builder()
      .method(Method::GET)
      .uri(url.as_str())
      .body(Full::new(Bytes::new()))
      .unwrap();
    let res = self.inner.call(req);
    Box::pin(async move {
      let res: Response<TyBody> = res.await.map_err(|e| HttpPearClientError::Send(format!("{e:?}")))?;
      let body = res
        .into_body()
        .collect()
        .await
        .map_err(|e| HttpPearClientError::Receive(format!("{e:?}")))?;
      let body: Bytes = body.to_bytes();
      let result = ReleaseListing::from_xml(body.as_ref());
      Ok(result)
    })
  }
}

impl<'req, Cx, TyInner, TyBody> Service<&'req GetPackageInfoQuery<Cx>> for HttpPearClient<TyInner>
where
  Cx: GetRef<PearUrl>,
  TyInner: Service<Request<Full<Bytes>>, Response = Response<TyBody>> + 'req,
  TyInner::Error: StdError,
  TyInner::Future: Send,
  TyBody: Body + Send,
  TyBody::Data: Send,
  TyBody::Error: StdError,
{
  type Response = PackageInfo;
  type Error = HttpPearClientError;
  type Future = BoxFuture<'req, Result<Self::Response, Self::Error>>;

  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    self
      .inner
      .poll_ready(cx)
      .map_err(|e| HttpPearClientError::PollReady(format!("{e:?}")))
  }

  fn call(&mut self, req: &'req GetPackageInfoQuery<Cx>) -> Self::Future {
    let url = req.context.get_ref().url_join(["p", req.package.as_str(), "info.xml"]);

    let req = Request::builder()
      .method(Method::GET)
      .uri(url.as_str())
      .body(Full::new(Bytes::new()))
      .unwrap();
    let res = self.inner.call(req);
    Box::pin(async move {
      let res: Response<TyBody> = res.await.map_err(|e| HttpPearClientError::Send(format!("{e:?}")))?;
      let body = res
        .into_body()
        .collect()
        .await
        .map_err(|e| HttpPearClientError::Receive(format!("{e:?}")))?;
      let body: Bytes = body.to_bytes();
      let result = PackageInfo::from_xml(body.as_ref());
      Ok(result)
    })
  }
}

impl<'req, Cx, TyInner, TyBody> Service<&'req GetReleaseQuery<Cx>> for HttpPearClient<TyInner>
where
  Cx: GetRef<PearUrl>,
  TyInner: Service<Request<Full<Bytes>>, Response = Response<TyBody>> + 'req,
  TyInner::Error: StdError,
  TyInner::Future: Send,
  TyBody: Body + Send,
  TyBody::Data: Send,
  TyBody::Error: StdError,
{
  type Response = Release;
  type Error = HttpPearClientError;
  type Future = BoxFuture<'req, Result<Self::Response, Self::Error>>;

  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    self
      .inner
      .poll_ready(cx)
      .map_err(|e| HttpPearClientError::PollReady(format!("{e:?}")))
  }

  fn call(&mut self, req: &'req GetReleaseQuery<Cx>) -> Self::Future {
    let url = req.context.get_ref().url_join(["r", req.package.as_str(), &format_compact!("{}.xml", req.version.as_str())]);

    let req = Request::builder()
      .method(Method::GET)
      .uri(url.as_str())
      .body(Full::new(Bytes::new()))
      .unwrap();
    let res = self.inner.call(req);
    Box::pin(async move {
      let res: Response<TyBody> = res.await.map_err(|e| HttpPearClientError::Send(format!("{e:?}")))?;
      let body = res
        .into_body()
        .collect()
        .await
        .map_err(|e| HttpPearClientError::Receive(format!("{e:?}")))?;
      let body: Bytes = body.to_bytes();
      let result = Release::from_xml(body.as_ref());
      Ok(result)
    })
  }
}