use std::error::Error;
use http::Error as HttpError;
use http::HeaderMap;
use http::Method;
use http::StatusCode;
use crate::Bytes;
use crate::Str;
pub trait Endpoint {
type Input;
type Output;
type Error: Error + From<HttpError> + From<Self::ConversionError> + 'static;
type ConversionError: Error;
type ApiError: Error;
fn base_url() -> Option<Str> {
None
}
fn method() -> Method {
Method::GET
}
fn path(input: &Self::Input) -> Str;
#[allow(unused)]
fn query(input: &Self::Input) -> Result<Option<Str>, Self::ConversionError> {
Ok(None)
}
#[allow(unused)]
fn headers(input: &Self::Input) -> Result<Option<HeaderMap>, Self::ConversionError> {
Ok(None)
}
#[allow(unused)]
fn body(input: &Self::Input) -> Result<Option<Bytes>, Self::ConversionError> {
Ok(None)
}
fn parse(body: &[u8]) -> Result<Self::Output, Self::ConversionError>;
fn parse_err(body: &[u8]) -> Result<Self::ApiError, Vec<u8>>;
#[doc(hidden)]
fn evaluate(status: StatusCode, body: &[u8]) -> Result<Self::Output, Self::Error>;
}
#[macro_export]
macro_rules! EndpointDef {
( $(#[$docs:meta])* $pub:vis $name:ident($in:ty),
Ok => $out:ty, [$($(#[$ok_docs:meta])* $ok_status:ident,)*],
Err => $err:ident, [$($(#[$err_docs:meta])* $err_status:ident => $variant:ident,)*],
ConversionErr => $conv_err:ty,
ApiErr => $api_err:ty,
$($defs:tt)* ) => {
$(#[$docs])*
#[derive(Clone, Copy, Debug)]
$pub struct $name;
#[allow(unused_qualifications)]
#[derive(Debug)]
$pub enum $err {
$(
$(#[$err_docs])*
$variant(Result<$api_err, Vec<u8>>),
)*
UnexpectedStatus(::http::StatusCode, Result<$api_err, Vec<u8>>),
Http(::http::Error),
Conversion($conv_err),
}
#[allow(unused_qualifications)]
impl ::std::fmt::Display for $err {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fn format_message(message: &Result<$api_err, Vec<u8>>) -> String {
match message {
Ok(err) => err.to_string(),
Err(body) => {
match ::std::str::from_utf8(&body) {
Ok(body) => format!("{}", body),
Err(err) => format!("{:?}", body),
}
},
}
}
match self {
$(
$err::$variant(message) => {
let status = ::http::StatusCode::$err_status;
let message = format_message(message);
write!(fmt, "HTTP status {}: {}", status, message)
},
)*
$err::UnexpectedStatus(status, message) => {
let message = format_message(message);
write!(fmt, "Unexpected HTTP status {}: {}", status, message)
},
$err::Http(err) => write!(fmt, "{}", err),
$err::Conversion(err) => write!(fmt, "{}", err),
}
}
}
#[allow(unused_qualifications)]
impl ::std::error::Error for $err {
fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
match self {
$(
$err::$variant(..) => None,
)*
$err::UnexpectedStatus(..) => None,
$err::Http(err) => err.source(),
$err::Conversion(err) => err.source(),
}
}
}
#[allow(unused_qualifications)]
impl ::std::convert::From<::http::Error> for $err {
fn from(src: ::http::Error) -> Self {
$err::Http(src)
}
}
#[allow(unused_qualifications)]
impl ::std::convert::From<$conv_err> for $err {
fn from(src: $conv_err) -> Self {
$err::Conversion(src)
}
}
#[allow(unused_qualifications)]
impl ::std::convert::From<$err> for ::http_endpoint::Error<$conv_err> {
fn from(src: $err) -> Self {
match src {
$(
$err::$variant(result) => {
let status = ::http::StatusCode::$err_status;
match result {
Ok(err) => {
::http_endpoint::Error::HttpStatus(status, err.to_string().into_bytes())
},
Err(data) => ::http_endpoint::Error::HttpStatus(status, data),
}
},
)*
$err::UnexpectedStatus(status, result) => {
match result {
Ok(err) => {
::http_endpoint::Error::HttpStatus(status, err.to_string().into_bytes())
},
Err(data) => ::http_endpoint::Error::HttpStatus(status, data),
}
},
$err::Http(err) => ::http_endpoint::Error::Http(err),
$err::Conversion(err) => ::http_endpoint::Error::Conversion(err),
}
}
}
#[allow(unused_qualifications)]
impl ::http_endpoint::Endpoint for $name {
type Input = $in;
type Output = $out;
type Error = $err;
type ConversionError = $conv_err;
type ApiError = $api_err;
$($defs)*
#[allow(unused_qualifications)]
fn evaluate(
status: ::http::StatusCode,
body: &[u8],
) -> Result<$out, $err> {
match status {
$(
::http::StatusCode::$ok_status => {
<$name as ::http_endpoint::Endpoint>::parse(&body).map_err($err::from)
},
)*
status => {
let res = <$name as ::http_endpoint::Endpoint>::parse_err(&body);
match status {
$(
::http::StatusCode::$err_status => {
Err($err::$variant(res))
},
)*
_ => Err($err::UnexpectedStatus(status, res)),
}
},
}
}
}
};
}