mod common;
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::str::FromStr as _;
use http::header::HeaderMap;
use http::header::HeaderName;
use http::header::HeaderValue;
use http::Method;
use http_endpoint::Bytes;
use http_endpoint::EndpointDef;
use http_endpoint::Str;
use serde::Deserialize;
use serde::Serialize;
use serde_json::from_slice;
use serde_json::to_string as to_json;
use serde_json::Error as JsonError;
use test_log::test;
use thiserror::Error as ThisError;
use common::issue;
use common::Error;
use common::NoError;
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Person {
name: String,
age: u8,
}
#[derive(Debug, Deserialize)]
struct Data<T> {
#[serde(rename = "json")]
data: T,
}
impl<T> Display for Data<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.data)
}
}
impl<T> StdError for Data<T> where T: StdError {}
EndpointDef! {
PostPerson(Str),
Ok => Data<Person>, [
OK,
],
Err => PostError, [],
ConversionErr => JsonError,
ApiErr => NoError,
fn method() -> Method {
Method::POST
}
fn path(_: &Self::Input) -> Str {
"/anything".into()
}
fn body(input: &Self::Input) -> Result<Option<Bytes>, Self::ConversionError> {
Ok(Some(input.to_string().into_bytes().into()))
}
fn parse(body: &[u8]) -> Result<Self::Output, Self::ConversionError> {
from_slice::<Self::Output>(body)
}
fn parse_err(body: &[u8]) -> Result<Self::ApiError, Vec<u8>> {
assert_eq!(body, &[0; 0]);
Ok(NoError)
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize, ThisError)]
#[error("{message} ({code})")]
struct ApiError {
message: String,
code: u8,
}
EndpointDef! {
PostApiError(Str),
Ok => (), [],
Err => PostApiErrorError, [
OK => Ok,
],
ConversionErr => JsonError,
ApiErr => Data<ApiError>,
fn method() -> Method {
Method::POST
}
fn path(_: &Self::Input) -> Str {
"/anything".into()
}
fn body(input: &Self::Input) -> Result<Option<Bytes>, Self::ConversionError> {
Ok(Some(input.to_string().into_bytes().into()))
}
fn parse(body: &[u8]) -> Result<Self::Output, Self::ConversionError> {
from_slice::<Self::Output>(body)
}
fn parse_err(body: &[u8]) -> Result<Self::ApiError, Vec<u8>> {
from_slice::<Self::ApiError>(body).map_err(|_| body.to_vec())
}
}
#[derive(Debug, Deserialize)]
struct Headers {
#[serde(rename = "headers")]
headers: HashMap<String, String>,
}
EndpointDef! {
GetHeaders(Vec<(&'static str, &'static str)>),
Ok => Headers, [
OK,
],
Err => GetError, [],
ConversionErr => JsonError,
ApiErr => NoError,
fn path(_: &Self::Input) -> Str {
"/headers".into()
}
fn headers(input: &Self::Input) -> Result<Option<HeaderMap>, Self::ConversionError> {
let mut headers = HeaderMap::with_capacity(input.len());
for (key, value) in input {
headers.append(HeaderName::from_str(key).unwrap(), HeaderValue::from_str(value).unwrap());
}
Ok(Some(headers))
}
fn parse(body: &[u8]) -> Result<Self::Output, Self::ConversionError> {
from_slice::<Self::Output>(body)
}
fn parse_err(body: &[u8]) -> Result<Self::ApiError, Vec<u8>> {
assert_eq!(body, &[0; 0]);
Ok(NoError)
}
}
#[test(tokio::test)]
async fn decode_json() {
let person = Person {
name: "Peter".to_string(),
age: 37,
};
let json = to_json(&person).unwrap();
let result = issue::<PostPerson>(&json.into()).await.unwrap();
assert_eq!(result.data, person);
}
#[test(tokio::test)]
async fn decode_json_error() {
let json = r#"{ foobar: invalid" }"#;
let err = issue::<PostPerson>(&json.into()).await.unwrap_err();
match err {
Error::Endpoint(PostError::Conversion(err)) => {
assert!(
err
.to_string()
.starts_with("invalid type: null, expected struct Person"),
"{}",
err,
)
},
_ => panic!("unexpected error: {:?}", err),
}
}
#[test(tokio::test)]
async fn decode_api_error() {
let api_error = ApiError {
message: "that's a failure".to_string(),
code: 42,
};
let json = to_json(&api_error).unwrap();
let err = issue::<PostApiError>(&json.into()).await.unwrap_err();
match err {
Error::Endpoint(PostApiErrorError::Ok(err)) => assert_eq!(err.unwrap().data, api_error),
_ => panic!("unexpected error: {:?}", err),
}
}
#[test(tokio::test)]
async fn request_headers() {
let headers = vec![("Foobar", "foobaz"), ("Another", "header")];
let response = issue::<GetHeaders>(&headers).await.unwrap();
assert_eq!(response.headers.get("Another").unwrap(), "header");
assert_eq!(response.headers.get("Foobar").unwrap(), "foobaz");
assert_eq!(response.headers.get("Foobaz"), None);
}