use body::Body;
use client::Client;
use config::ClientConfig;
use error::Error;
use request::{Request, RequestHeader};
use reqwest::header::HeaderMap;
use reqwest::{Method, StatusCode, Url};
use response::Response;
use std::collections::{BTreeMap, HashMap};
mod settings;
pub use self::settings::{StubDefault, StubSettings, StubStrictness};
mod builder;
pub use self::builder::{RequestStubber, ResponseStubber};
mod error;
pub use self::error::RegisterStubError;
pub use self::error::FieldError;
#[derive(Hash, PartialEq, Eq)]
struct StubKey {
url: Url,
method: Option<Method>,
body: Option<Vec<u8>>,
headers: Option<BTreeMap<String, String>>,
}
struct StubRequest {
url: Url,
method: Option<Method>,
body: Option<Body>,
headers: Option<BTreeMap<String, String>>,
}
impl StubRequest {
fn try_to_key(self) -> Result<StubKey, ::std::io::Error> {
Ok(StubKey {
url: self.url,
method: self.method,
body: match self.body {
Some(b) => Some(b.try_to_vec()?),
None => None,
},
headers: self.headers,
})
}
}
struct StubResponse {
status_code: StatusCode,
body: Option<Body>,
headers: HeaderMap,
}
pub struct StubClient {
config: ClientConfig,
stubs: HashMap<StubKey, Response>,
settings: StubSettings,
}
impl StubClient {
pub fn new(stub_settings: StubSettings) -> Self {
StubClient {
config: ClientConfig::default(),
stubs: HashMap::new(),
settings: stub_settings,
}
}
pub fn stub<'cl>(&'cl mut self, url: Url) -> RequestStubber<'cl> {
RequestStubber::new(self, url)
}
fn stub_key(&self, header: &RequestHeader, body: &Option<Vec<u8>>) -> StubKey {
match self.settings.strictness {
StubStrictness::Full => StubKey {
url: header.url.clone(),
method: Some(header.method.clone()),
body: body.clone(),
headers: Some(::helper::serialize_headers(&header.headers)),
},
StubStrictness::BodyMethodUrl => StubKey {
url: header.url.clone(),
method: Some(header.method.clone()),
body: body.clone(),
headers: None,
},
StubStrictness::HeadersMethodUrl => StubKey {
url: header.url.clone(),
method: Some(header.method.clone()),
body: None,
headers: Some(::helper::serialize_headers(&header.headers)),
},
StubStrictness::MethodUrl => StubKey {
url: header.url.clone(),
method: Some(header.method.clone()),
body: None,
headers: None,
},
StubStrictness::Url => StubKey {
url: header.url.clone(),
method: None,
body: None,
headers: None,
},
}
}
pub(self) fn register_stub(
&mut self,
key: StubKey,
value: StubResponse,
) -> Result<(), RegisterStubError> {
macro_rules! validate_sk_field {
(Some $field:ident $strictness:path) => {
if key.$field.is_none() {
return Err(RegisterStubError::MissingField(FieldError {
field_name: stringify!($field),
strictness: stringify!($strictness),
}));
}
};
(None $field:ident $strictness:path) => {
if key.$field.is_some() {
return Err(RegisterStubError::UnescessaryField(FieldError {
field_name: stringify!($field),
strictness: stringify!($strictness),
}));
}
};
}
macro_rules! validate_sk_fields {
( $strictness:path; $($sn:tt $field:ident),* )
=> ( $( validate_sk_field!($sn $field $strictness); )* )
}
match self.settings.strictness {
StubStrictness::Full => {
validate_sk_fields!(StubStrictness::Full; Some method, Some body, Some headers);
}
StubStrictness::BodyMethodUrl => {
validate_sk_fields!(StubStrictness::BodyMethodUrl; Some method, Some body, None headers);
}
StubStrictness::HeadersMethodUrl => {
validate_sk_fields!(StubStrictness::HeadersMethodUrl; Some method, None body, Some headers);
}
StubStrictness::MethodUrl => {
validate_sk_fields!(StubStrictness::MethodUrl; Some method, None body, None headers);
}
StubStrictness::Url => {
validate_sk_fields!(StubStrictness::Url; None method, None body, None headers);
}
}
let response = Response {
url: key.url.clone(),
status: value.status_code,
headers: value.headers,
body: value
.body
.map(|b| b.try_to_vec())
.unwrap_or_else(|| Ok(Vec::new()))
.map_err(|e| RegisterStubError::ReadFile(e))?,
};
self.stubs.insert(key, response);
Ok(())
}
}
impl Client for StubClient {
fn execute(&self, config: Option<&ClientConfig>, request: Request) -> Result<Response, Error> {
let header = request.header;
let body = match request.body {
Some(b) => Some(b.try_to_vec()?),
None => None,
};
let key = self.stub_key(&header, &body);
match self.stubs.get(&key) {
Some(resp) => Ok(resp.clone()),
None => {
match self.settings.default {
StubDefault::Panic => {
panic!(
"Requested {}, without having provided a stub for it.",
header.url
);
}
StubDefault::Error => {
Err(format!(
"Requested {}, without having provided a stub for it.",
header.url
)
.into())
}
StubDefault::PerformRequest => {
use client::DirectClient;
let client = DirectClient::new();
let request = Request {
header: header,
body: body.map(Body::from),
};
client.execute(config, request)
}
}
}
}
}
fn config(&self) -> &ClientConfig {
&self.config
}
fn config_mut(&mut self) -> &mut ClientConfig {
&mut self.config
}
}