use std::rc::Rc;
use ntex::http::header::{HeaderName, HeaderValue};
use ntex::{Service, ServiceCtx, Middleware};
use ntex::web::{WebRequest, WebResponse, Error, ErrorRenderer, HttpResponse};
use futures::future::{ok, Either, Ready, LocalBoxFuture};
struct Inner {
version: String,
}
pub struct Versioning {
config: Inner,
}
impl Versioning {
pub fn new(version: &str) -> Self {
Self {
config: Inner {
version: version.to_owned(),
},
}
}
pub fn finish(self) -> VersioningFactory {
VersioningFactory {
inner: Rc::new(self.config),
}
}
}
pub struct VersioningFactory {
inner: Rc<Inner>,
}
impl<S> Middleware<S> for VersioningFactory {
type Service = VersioningMiddleware<S>;
fn create(&self, service: S) -> Self::Service {
VersioningMiddleware {
service,
inner: self.inner.clone(),
}
}
}
pub struct VersioningMiddleware<S> {
service: S,
inner: Rc<Inner>,
}
impl<S, Err> Service<WebRequest<Err>> for VersioningMiddleware<S>
where
S: Service<WebRequest<Err>, Response = WebResponse, Error = Error>,
Err: ErrorRenderer,
{
type Response = WebResponse;
type Error = Error;
type Future<'f> = Either<LocalBoxFuture<'f, Result<Self::Response, S::Error>>, Ready<Result<Self::Response, S::Error>>> where Self: 'f;
ntex::forward_poll_ready!(service);
fn call<'a>(
&'a self,
mut req: WebRequest<Err>,
ctx: ServiceCtx<'a, Self>,
) -> Self::Future<'_> {
let version = req.match_info_mut().get("version");
let header_name = HeaderName::from_static("x-api-version");
let header_value = HeaderValue::from_str(&self.inner.version)
.unwrap_or(HeaderValue::from_static("error"));
if let Some(version) = version {
let version_number = version.replace('v', "");
let versions = version_number.split('.').collect::<Vec<&str>>();
let major = versions.first();
let minor = versions.get(1);
let current_versions =
self.inner.version.split('.').collect::<Vec<&str>>();
let current_major = current_versions.first();
let current_minor = current_versions.get(1);
if minor.is_none()
|| major.is_none()
|| current_major.is_none()
|| current_minor.is_none()
{
let msg = format!("{version} is invalid");
let mut res = req.into_response(
HttpResponse::NotFound()
.json(&serde_json::json!({
"msg": msg,
}))
.into_body(),
);
res.headers_mut().insert(header_name, header_value);
return Either::Right(ok(res));
}
let minor_number = minor.unwrap().parse::<usize>();
let major_number = major.unwrap().parse::<usize>();
let current_minor_number = current_minor.unwrap().parse::<usize>();
let current_major_number = current_major.unwrap().parse::<usize>();
if minor_number.is_err()
|| major_number.is_err()
|| current_minor_number.is_err()
|| current_major_number.is_err()
{
let msg = format!("{version} is not a number");
let mut res = req.into_response(
HttpResponse::NotFound()
.json(&serde_json::json!({
"msg": msg,
}))
.into_body(),
);
res.headers_mut().insert(header_name, header_value);
return Either::Right(ok(res));
}
let minor_number = minor_number.unwrap();
let major_number = major_number.unwrap();
let current_minor_number = current_minor_number.unwrap();
let current_major_number = current_major_number.unwrap();
if major_number > current_major_number
|| (major_number == current_major_number
&& minor_number > current_minor_number)
{
let msg = format!("{version} is not supported");
let mut res = req.into_response(
HttpResponse::NotFound()
.json(&serde_json::json!({
"msg": msg,
}))
.into_body(),
);
res.headers_mut().insert(header_name, header_value);
return Either::Right(ok(res));
}
}
Either::Left(Box::pin(async move {
let mut res = ctx.call(&self.service, req).await?;
res.headers_mut().insert(header_name, header_value);
Ok(res)
}))
}
}